diff --git a/.cargo/config.toml b/.cargo/config.toml index c65cd3ac1a4fa..52565a593c056 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -33,5 +33,10 @@ rustflags = [ [target.'cfg(all())'] rustflags = ["--cfg", "tokio_unstable"] -[unstable] -lints = true +# We have large git dependencies. This can make cloning faster. +# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#git +# Actually we also want to prevent submodule cloning completely +# https://github.com/rust-lang/cargo/issues/4247 +[unstable.git] +shallow_index = true +shallow_deps = true diff --git a/.github/pr-title-checker-config.json b/.github/pr-title-checker-config.json index 958bbba56d2c5..2da13e6e86f5c 100644 --- a/.github/pr-title-checker-config.json +++ b/.github/pr-title-checker-config.json @@ -4,7 +4,7 @@ "color": "B60205" }, "CHECKS": { - "regexp": "^(cherry pick|cherry-pick)?(| )+(feat|fix|test|refactor|chore|style|doc|perf|build|ci|revert|deprecate)(\\(.*\\))?:.*", + "regexp": "^(cherry pick|cherry-pick)?(| |:|: )+(feat|fix|test|refactor|chore|style|doc|perf|build|ci|revert|deprecate)(\\(.*\\))?:.*", "ignoreLabels" : ["ignore-title"] }, "MESSAGES": { diff --git a/.github/workflows/auto-update-helm-and-operator-version-by-release.yml b/.github/workflows/auto-update-helm-and-operator-version-by-release.yml new file mode 100644 index 0000000000000..1e4e6a8c9c1e9 --- /dev/null +++ b/.github/workflows/auto-update-helm-and-operator-version-by-release.yml @@ -0,0 +1,79 @@ +name: Update Helm Charts and Risingwave Operator on New Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + version: + description: 'release version' + required: true + +env: + NEW_APP_VERSION: ${{ github.event.inputs.version || github.event.release.tag_name }} + +jobs: + update-helm-charts: + runs-on: ubuntu-latest + steps: + - name: Checkout Helm Charts Repository + uses: actions/checkout@v3 + with: + repository: 'risingwavelabs/helm-charts' + token: ${{ secrets.PR_TOKEN }} + path: 'helm-charts' + + - name: Update values.yaml + run: | + sed -i "s/^ tag:.*/ tag: \"${{ env.NEW_APP_VERSION }}\"/" helm-charts/charts/risingwave/values.yaml + + - name: Update Chart.yaml + run: | + cd helm-charts/charts/risingwave + CURRENT_VERSION=$(grep 'version:' Chart.yaml | awk '{print $2}' | head -n 1) + NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. -v OFS='.' '{$NF++; print}') + sed -i "/type: application/,/version:/!b; /version:/s/version: .*/version: $NEW_VERSION/" Chart.yaml + sed -i "s/^appVersion: .*/appVersion: \"${{ env.NEW_APP_VERSION }}\"/" Chart.yaml + echo "NEW_CHART_VERSION=$NEW_VERSION" >> $GITHUB_ENV + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.PR_TOKEN }} + commit-message: 'chore: bump risingwave to ${{ env.NEW_APP_VERSION }}, release chart ${{ env.NEW_CHART_VERSION }}' + title: 'chore: bump risingwave to ${{ env.NEW_APP_VERSION }}, release chart ${{ env.NEW_CHART_VERSION }}' + body: 'This is an automated pull request to update the chart versions' + branch: 'auto-update-${{ env.NEW_APP_VERSION }}' + path: 'helm-charts' + reviewers: arkbriar + delete-branch: true + signoff: true + + update-risingwave-operator: + runs-on: ubuntu-latest + steps: + - name: Checkout Risingwave Operator Repository + uses: actions/checkout@v3 + with: + repository: 'risingwavelabs/risingwave-operator' + token: ${{ secrets.PR_TOKEN }} + path: 'risingwave-operator' + + - name: Update risingwave-operator image tags + run: | + cd risingwave-operator + PREV_VERSION=$(grep -roh "risingwavelabs/risingwave:v[0-9\.]*" * | head -n 1 | cut -d':' -f2) + grep -rl "risingwavelabs/risingwave:$PREV_VERSION" . | xargs sed -i "s|risingwavelabs/risingwave:$PREV_VERSION|risingwavelabs/risingwave:${{ env.NEW_APP_VERSION }}|g" + + - name: Create Pull Request for risingwave-operator + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.PR_TOKEN }} + commit-message: 'chore: bump risingwave image tags to ${{ env.NEW_APP_VERSION }}' + title: 'chore: bump risingwave image tags to ${{ env.NEW_APP_VERSION }}' + body: 'This is an automated pull request to update the risingwave image tags' + branch: 'auto-update-${{ env.NEW_APP_VERSION }}' + path: 'risingwave-operator' + reviewers: arkbriar + delete-branch: true + signoff: true diff --git a/.github/workflows/cherry-pick-to-release-branch.yml b/.github/workflows/cherry-pick-to-release-branch.yml index f54e9fc722e6e..18dba108047dd 100644 --- a/.github/workflows/cherry-pick-to-release-branch.yml +++ b/.github/workflows/cherry-pick-to-release-branch.yml @@ -6,8 +6,8 @@ on: types: ["closed", "labeled"] jobs: - release_pull_request_1_8: - if: "contains(github.event.pull_request.labels.*.name, 'need-cherry-pick-release-1.8') && github.event.pull_request.merged == true" + release_pull_request_1_9: + if: "contains(github.event.pull_request.labels.*.name, 'need-cherry-pick-release-1.9') && github.event.pull_request.merged == true" runs-on: ubuntu-latest name: release_pull_request steps: @@ -16,14 +16,14 @@ jobs: - name: Create PR to branch uses: risingwavelabs/github-action-cherry-pick@master with: - pr_branch: 'release-1.8' + pr_branch: 'release-1.9' pr_labels: 'cherry-pick' - pr_body: ${{ format('Cherry picking \#{0} onto branch release-1.8', github.event.number) }} + pr_body: ${{ format('Cherry picking \#{0} onto branch release-1.9', github.event.number) }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release_pull_request_1_7_standalone: - if: "contains(github.event.pull_request.labels.*.name, 'need-cherry-pick-release-1.7-standalone') && github.event.pull_request.merged == true" + release_pull_request_1_10: + if: "contains(github.event.pull_request.labels.*.name, 'need-cherry-pick-release-1.10') && github.event.pull_request.merged == true" runs-on: ubuntu-latest name: release_pull_request steps: @@ -32,24 +32,9 @@ jobs: - name: Create PR to branch uses: risingwavelabs/github-action-cherry-pick@master with: - pr_branch: 'release-1.7.0-standalone' + pr_branch: 'release-1.10' pr_labels: 'cherry-pick' - pr_body: ${{ format('Cherry picking \#{0} onto branch release-1.7.0-standalone', github.event.number) }} + pr_body: ${{ format('Cherry picking \#{0} onto branch release-1.10', github.event.number) }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - release_pull_request_1_7: - if: "contains(github.event.pull_request.labels.*.name, 'need-cherry-pick-release-1.7') && github.event.pull_request.merged == true" - runs-on: ubuntu-latest - name: release_pull_request - steps: - - name: checkout - uses: actions/checkout@v1 - - name: Create PR to branch - uses: risingwavelabs/github-action-cherry-pick@master - with: - pr_branch: 'release-1.7' - pr_labels: 'cherry-pick' - pr_body: ${{ format('Cherry picking \#{0} onto branch release-1.7', github.event.number) }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/connector-node-integration.yml b/.github/workflows/connector-node-integration.yml index f20f209a3953d..abc1942055fb5 100644 --- a/.github/workflows/connector-node-integration.yml +++ b/.github/workflows/connector-node-integration.yml @@ -2,11 +2,11 @@ name: Connector Node Integration Tests on: push: - branches: [main] + branches: [ main ] pull_request: - branches: [main] + branches: [ main ] merge_group: - types: [checks_requested] + types: [ checks_requested ] jobs: build: @@ -42,4 +42,8 @@ jobs: echo "--- build connector node" cd ${RISINGWAVE_ROOT}/java # run unit test - mvn --batch-mode --update-snapshots clean package -Dno-build-rust + # WARN: `testOnNext_writeValidation` is skipped because it relies on Rust code to decode message, + # while we don't build Rust code (`-Dno-build-rust`) here to save time + mvn --batch-mode --update-snapshots clean package -Dno-build-rust \ + '-Dtest=!com.risingwave.connector.sink.SinkStreamObserverTest#testOnNext_writeValidation' \ + -Dsurefire.failIfNoSpecifiedTests=false diff --git a/.github/workflows/dashboard_main.yml b/.github/workflows/dashboard.yml similarity index 77% rename from .github/workflows/dashboard_main.yml rename to .github/workflows/dashboard.yml index 0b9571b35154d..c722d4704e00e 100644 --- a/.github/workflows/dashboard_main.yml +++ b/.github/workflows/dashboard.yml @@ -1,8 +1,11 @@ -name: Dashboard (main) +name: Dashboard on: push: branches: [main] paths: [dashboard/**, proto/**] + pull_request: + branches: [main] + paths: [dashboard/**, proto/**] workflow_dispatch: jobs: dashboard-ui-deploy: @@ -19,11 +22,14 @@ jobs: - name: build working-directory: ./dashboard run: | + echo "::group::npm install" npm install + echo "::endgroup::" npm run lint npm run build - name: Deploy uses: s0/git-publish-subdir-action@develop + if: github.event_name == 'push' && github.ref == 'refs/heads/main' env: REPO: self BRANCH: dashboard-artifact diff --git a/.github/workflows/dashboard_pr.yml b/.github/workflows/dashboard_pr.yml deleted file mode 100644 index c7245a2119d99..0000000000000 --- a/.github/workflows/dashboard_pr.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Dashboard -on: - pull_request: - branches: [main] - paths: [dashboard/**, proto/**] - -concurrency: - group: dashboard-build-${{ github.ref }} - cancel-in-progress: true - -jobs: - dashboard-ui-build: - runs-on: ubuntu-latest - steps: - - name: Checkout forked repo - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - - uses: actions/setup-node@v2 - with: - node-version: "18" - - uses: arduino/setup-protoc@v1 - with: - version: "3.x" - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: build - working-directory: ./dashboard - run: | - echo "::group::npm install" - npm install - echo "::endgroup::" - npm run gen-proto - - git add -A - if ! git diff --quiet --staged; then - git reset - echo "::error::Generated proto files are not up to date." - - echo "::group::check if the branch is out-of-date" - git pull origin $GITHUB_HEAD_REF --unshallow - git remote add upstream https://github.com/risingwavelabs/risingwave.git - git fetch upstream $GITHUB_BASE_REF:$GITHUB_BASE_REF - - echo "origin/$GITHUB_HEAD_REF: $(git rev-parse origin/$GITHUB_HEAD_REF)" - echo "upstream/$GITHUB_BASE_REF: $(git rev-parse upstream/$GITHUB_BASE_REF)" - - git merge-base origin/$GITHUB_HEAD_REF upstream/$GITHUB_BASE_REF - - FORK_POINT=$(git merge-base origin/$GITHUB_HEAD_REF upstream/$GITHUB_BASE_REF) - echo "FORK_POINT: $FORK_POINT" - echo "::endgroup::" - - if ! git diff --quiet $FORK_POINT..upstream/$GITHUB_BASE_REF -- ../proto; then - echo "::error::Your branch is out-of-date. Please update your branch first and then run 'npm i && npm run gen-proto' at dashboard/ and commit the changes." - else - echo "::error::Please run 'npm i && npm run gen-proto' at dashboard/ and commit the changes." - fi - exit 1 - fi - - npm run lint - npm run build diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 35180a9ea9a67..1b4429c17d19c 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -25,13 +25,13 @@ jobs: remove-docker-images: 'true' root-reserve-mb: 10240 temp-reserve-mb: 10240 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Rust toolchain run: rustup show - - name: Install dependencies + - name: Install dependencies for compiling RisingWave run: sudo apt-get update && sudo apt-get install -y make build-essential cmake protobuf-compiler curl openssl libssl-dev libsasl2-dev libcurl4-openssl-dev pkg-config postgresql-client tmux lld - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.3 + uses: mozilla-actions/sccache-action@v0.0.5 with: version: "v0.5.2" - name: build rustdocs @@ -39,14 +39,14 @@ jobs: cargo doc --workspace --no-deps --document-private-items cp docs/rustdoc/rust.css target/doc/rust.css - mkdir artifact - cp -R target/doc/* artifact + mkdir -p artifact/rustdoc + cp -R target/doc/* artifact/rustdoc - name: Show available storage run: df -h - - name: Install cargo-docset + - name: Install tools for building docs uses: taiki-e/install-action@v2 with: - tool: cargo-docset + tool: cargo-docset,mdbook,mdbook-toc,mdbook-linkcheck - name: build docsets run: | cargo docset --no-clean --docset-name RisingWave @@ -58,8 +58,13 @@ jobs: " > RisingWave.xml cp -t artifact "risingwave.docset.tgz" "RisingWave.xml" + - name: build developer doc + run: | + cd docs/dev + mdbook build + cp -R book/html/* ../../artifact - name: Upload artifacts - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: path: artifact - name: Show available storage @@ -76,4 +81,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/nightly-rust.yml b/.github/workflows/nightly-rust.yml index eab964fafa177..5fa90ae1138c8 100644 --- a/.github/workflows/nightly-rust.yml +++ b/.github/workflows/nightly-rust.yml @@ -5,22 +5,25 @@ name: Build with Latest Nightly Rust on: schedule: - cron: "0 0 * * *" + push: + branches: + - xxchan/latest-nightly-rust workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - - name: Maximize build space - uses: easimon/maximize-build-space@master - with: - remove-dotnet: 'true' - remove-android: 'true' - remove-haskell: 'true' - remove-codeql: 'true' - remove-docker-images: 'true' - root-reserve-mb: 2048 - swap-size-mb: 8192 + - name: Maximize build space + uses: easimon/maximize-build-space@master + with: + remove-dotnet: 'true' + remove-android: 'true' + remove-haskell: 'true' + remove-codeql: 'true' + remove-docker-images: 'true' + root-reserve-mb: 10240 + temp-reserve-mb: 10240 - uses: actions/checkout@v3 if: ${{ github.event_name == 'schedule' }} with: @@ -40,3 +43,5 @@ jobs: export CARGO_INCREMENTAL=0 export CARGO_PROFILE_DEV_DEBUG=false cargo check + - name: Show available storage + run: df -h diff --git a/.github/workflows/package_version_check.yml b/.github/workflows/package_version_check.yml new file mode 100644 index 0000000000000..e0bbd04eef12a --- /dev/null +++ b/.github/workflows/package_version_check.yml @@ -0,0 +1,57 @@ +name: Package Version Checker + +on: + pull_request: + branches: + - 'main' + +jobs: + compare-package-version-with-latest-release-version: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: List branches + run: | + git fetch --all + release_branches=$(git branch -r | grep -E 'origin/release-[0-9]+\.[0-9]+' | sed 's/origin\///') + echo "Release branches:" + echo "$release_branches" + echo "$release_branches" > release_branches.txt + + - name: Pick latest release branch + run: | + release_branches=$(cat release_branches.txt) + latest_branch=$(echo "$release_branches" | sort -t. -k1,1 -k2,2 -Vr | head -n 1) + echo "Latest release branch: $latest_branch" + latest_version=$(echo "$latest_branch" | sed -E 's/release-([0-9]+\.[0-9]+)/\1/' | sed 's/^[ \t]*//') + echo "Latest release version: $latest_version" + echo "$latest_version" > latest_release_version.txt + + - name: Read Cargo.toml version + run: | + cargo_version=$(grep -oP '(?<=^version = ")[0-9]+\.[0-9]+' Cargo.toml) + echo "Cargo.toml version: $cargo_version" + echo "$cargo_version" > cargo_version.txt + + - name: Compare versions + run: | + latest_version=$(cat latest_release_version.txt) + cargo_version=$(cat cargo_version.txt) + + latest_major=$(echo $latest_version | cut -d. -f1) + latest_minor=$(echo $latest_version | cut -d. -f2) + + cargo_major=$(echo $cargo_version | cut -d. -f1) + cargo_minor=$(echo $cargo_version | cut -d. -f2) + + if [ "$cargo_major" -lt "$latest_major" ] || { [ "$cargo_major" -eq "$latest_major" ] && [ "$cargo_minor" -le "$latest_minor" ]; }; then + echo "Error: Cargo.toml package version $cargo_version is not larger than $latest_version" + exit 1 + else + echo "Cargo.toml version $cargo_version is larger than or equal to $latest_version" + fi diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index eb5173e599e7d..c2389fc829b07 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -3,25 +3,50 @@ name: Mark stale issues and pull requests on: schedule: - cron: '30 1 * * *' + workflow_dispatch: + inputs: + # https://github.com/marketplace/actions/close-stale-issues#operations-per-run + operationsPerRun: + description: 'Max number of operations per run' + required: true + default: 30 jobs: stale: - runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - - uses: actions/stale@v5 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: > + stale-issue-message: | This issue has been open for 60 days with no activity. - Could you please update the status? Feel free to continue discussion or close as not planned. - stale-pr-message: > + + If you think it is still relevant today, and needs to be done *in the near future*, you can comment to update the status, or just manually remove the `no-issue-activity` label. + + You can also confidently close this issue as not planned to keep our backlog clean. + Don't worry if you think the issue is still valuable to continue in the future. + It's searchable and can be reopened when it's time. 😄 + stale-pr-message: | This PR has been open for 60 days with no activity. - Could you please update the status? Feel free to ping a reviewer if you are waiting for review. + + If it's blocked by code review, feel free to ping a reviewer or ask someone else to review it. + + If you think it is still relevant today, and have time to work on it *in the near future*, you can comment to update the status, or just manually remove the `no-pr-activity` label. + + You can also confidently close this PR to keep our backlog clean. (If no further action taken, the PR will be automatically closed after 7 days. Sorry! 🙏) + Don't worry if you think the PR is still valuable to continue in the future. + It's searchable and can be reopened when it's time. 😄 + close-pr-message: | + Close this PR as there's no further actions taken after it is marked as stale for 7 days. Sorry! 🙏 + + You can reopen it when you have time to continue working on it. stale-issue-label: 'no-issue-activity' stale-pr-label: 'no-pr-activity' days-before-close: -1 + days-before-pr-close: 7 + operations-per-run: ${{ github.event.inputs.operationsPerRun || 30 }} + enable-statistics: true diff --git a/.gitignore b/.gitignore index b74124ca9c157..25397584e2ef5 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,11 @@ simulation-it-test.tar.zst # spark binary e2e_test/iceberg/spark-*-bin* -**/poetry.lock \ No newline at end of file +**/poetry.lock + +*.slt.temp + +.direnv/ + +# mdbook +book diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000000..ab8ba3d9d7eb9 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +# Usage: install pre-commit, and then run `pre-commit install` to install git hooks +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: local + hooks: + - id: rustfmt + name: rustfmt + entry: rustfmt --edition 2021 + language: system + types: [rust] + - id: typos + name: typos + entry: typos -w + language: system + - id: cargo sort + name: cargo sort + entry: cargo sort -g -w + language: system + files: 'Cargo.toml' + pass_filenames: false diff --git a/.typos.toml b/.typos.toml index c062e9de44d2d..a7b5570bb766d 100644 --- a/.typos.toml +++ b/.typos.toml @@ -6,9 +6,10 @@ inout = "inout" # This is a SQL keyword! numer = "numer" # numerator nd = "nd" # N-dimentional / 2nd steam = "stream" # You played with Steam games too much. +ser = "ser" # Serialization # Some weird short variable names ot = "ot" -bui = "bui" # BackwardUserIterator +bui = "bui" # BackwardUserIterator mosquitto = "mosquitto" # This is a MQTT broker. abd = "abd" iy = "iy" @@ -21,8 +22,10 @@ extend-exclude = [ "e2e_test", "**/*.svg", "scripts", + "src/sqlparser/tests/testdata/", "src/frontend/planner_test/tests/testdata", "src/tests/sqlsmith/tests/freeze", + "src/license/src/manager.rs", "Cargo.lock", "**/Cargo.toml", "**/go.mod", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c0b3991fc1f61..7403b83afb698 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,72 +1,16 @@ -# Contribution guidelines +# Contributing to RisingWave -Thanks for your interest in contributing to RisingWave! We welcome and appreciate contributions. +Contributing documentation has moved to the **[RisingWave Developer Guide](https://risingwavelabs.github.io/risingwave/)**. -This document describes how to submit your code changes. To learn about the development process, see the [developer guide](docs/developer-guide.md). To understand the design and implementation of RisingWave, refer to the design docs listed in [docs/README.md](docs/README.md). -If you have questions, you can search for existing discussions or start a new discussion in the [Discussions forum of RisingWave](https://github.com/risingwavelabs/risingwave/discussions), or ask in the RisingWave Community channel on Slack. Please use the [invitation link](https://risingwave.com/slack) to join the channel. +## Before hacking on RisingWave -To report bugs, create a [GitHub issue](https://github.com/risingwavelabs/risingwave/issues/new/choose). +We encourage people to discuss their design before hacking on code. Typically, +you [file an issue] or start a thread on the [Community Slack] before submitting +a pull request. +Please read [the process] of how to submit your change to RisingWave. -## Table of contents - -- [Contribution guidelines](#contribution-guidelines) - - [Table of contents](#table-of-contents) - - [Tests and miscellaneous checks](#tests-and-miscellaneous-checks) - - [Submit a PR](#submit-a-pr) - - [Pull Request title](#pull-request-title) - - [Pull Request description](#pull-request-description) - - [Sign the CLA](#sign-the-cla) - - [Cherry pick the commit to release candidate branch](#cherry-pick-the-commit-to-release-candidate-branch) - -## Tests and miscellaneous checks - -Before submitting your code changes, ensure you fully test them and perform necessary checks. The testing instructions and necessary checks are detailed in the [developer guide](docs/developer-guide.md#test-your-code-changes). - -## Submit a PR - -### Pull Request title - -As described in [here](https://github.com/commitizen/conventional-commit-types/blob/master/index.json), a valid PR title should begin with one of the following prefixes: - -- `feat`: A new feature -- `fix`: A bug fix -- `doc`: Documentation only changes -- `refactor`: A code change that neither fixes a bug nor adds a feature -- `style`: A refactoring that improves code style -- `perf`: A code change that improves performance -- `test`: Adding missing tests or correcting existing tests -- `build`: Changes that affect the build system or external dependencies (example scopes: `.config`, `.cargo`, `Cargo.toml`) -- `ci`: Changes to RisingWave CI configuration files and scripts (example scopes: `.github`, `ci` (Buildkite)) -- `chore`: Other changes that don't modify src or test files -- `revert`: Reverts a previous commit - -For example, a PR title could be: - -- `refactor: modify executor protobuf package path` -- `feat(execution): enable comparison between nullable data arrays`, where `(execution)` means that this PR mainly focuses on the execution component. - -You may also check out previous PRs in the [PR list](https://github.com/risingwavelabs/risingwave/pulls). - -### Pull Request description - -- If your PR is small (such as a typo fix), you can go brief. -- If it is large and you have changed a lot, it's better to write more details. - -### Sign the CLA - -Contributors will need to sign RisingWave Labs' CLA. - -### Cherry pick the commit to release candidate branch -We have a GitHub Action to help cherry-pick commits from `main` branch to a `release candidate` branch, such as `v*.*.*-rc` where `*` is a number. - -Checkout details at: https://github.com/risingwavelabs/risingwave/blob/main/.github/workflows/cherry-pick-to-release-branch.yml - -To trigger the action, we give a correct label to the PR on `main` branch : -https://github.com/risingwavelabs/risingwave/blob/main/.github/workflows/cherry-pick-to-release-branch.yml#L10 - -It will act when the PR on `main` branch merged: -- If `git cherry-pick` does not find any conflicts, it will open a PR to the `release candidate` branch, and assign the original author as the reviewer. - -- If there is a conflict, it will open an issue and make the original author the assignee. \ No newline at end of file +[Community Slack]: https://risingwave.com/slack +[file an issue]: https://github.com/risingwavelabs/risingwave/issues/new/choose +[the process]: https://risingwavelabs.github.io/risingwave/contribution diff --git a/Cargo.lock b/Cargo.lock index 832ce69fa4db8..16e835d078f17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,11 +77,27 @@ dependencies = [ "aes", ] +[[package]] +name = "aes-siv" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e08d0cdb774acd1e4dac11478b1a0c0d203134b2aab0ba25eb430de9b18f8b9" +dependencies = [ + "aead", + "aes", + "cipher", + "cmac", + "ctr", + "dbl", + "digest", + "zeroize", +] + [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -90,9 +106,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "const-random", @@ -215,9 +231,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" dependencies = [ "backtrace", ] @@ -225,7 +241,7 @@ dependencies = [ [[package]] name = "apache-avro" version = "0.16.0" -source = "git+https://github.com/risingwavelabs/avro?rev=f33bf6a9d1734d1e23edeb374dc48d26db4b18a5#f33bf6a9d1734d1e23edeb374dc48d26db4b18a5" +source = "git+https://github.com/risingwavelabs/avro?rev=25113ba88234a9ae23296e981d8302c290fdaa4b#25113ba88234a9ae23296e981d8302c290fdaa4b" dependencies = [ "bzip2", "crc32fast", @@ -252,10 +268,10 @@ dependencies = [ [[package]] name = "apache-avro" version = "0.17.0" -source = "git+https://github.com/icelake-io/avro.git?branch=icelake-dev#4b828e9283e7248fd3ca42f5b590c2160b201785" +source = "git+https://github.com/apache/avro.git#fdab5db0816e28e3e10c87910c8b6f98c33072dc" dependencies = [ "apache-avro-derive", - "bigdecimal 0.4.2", + "bigdecimal 0.4.5", "digest", "libflate", "log", @@ -265,23 +281,23 @@ dependencies = [ "regex-lite", "serde", "serde_json", - "strum 0.25.0", - "strum_macros 0.25.3", + "strum 0.26.2", + "strum_macros 0.26.4", "thiserror", - "typed-builder 0.18.0", + "typed-builder 0.18.2", "uuid", ] [[package]] name = "apache-avro-derive" version = "0.17.0" -source = "git+https://github.com/icelake-io/avro.git?branch=icelake-dev#4b828e9283e7248fd3ca42f5b590c2160b201785" +source = "git+https://github.com/apache/avro.git#fdab5db0816e28e3e10c87910c8b6f98c33072dc" dependencies = [ - "darling 0.20.8", + "darling 0.20.9", "proc-macro2", "quote", "serde_json", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -296,6 +312,21 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "array-util" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c4dd5139f13c1a8b0e9f52197dcda016bbcd4e877055f93fb9ecd0f6c6136a7" +dependencies = [ + "arrayvec", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -314,7 +345,7 @@ version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8919668503a4f2d8b6da96fa7c16e93046bfb3412ffcfa1e5dc7d2e3adcb378" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-arith 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", @@ -327,7 +358,26 @@ dependencies = [ "arrow-row 48.0.1", "arrow-schema 48.0.1", "arrow-select 48.0.1", - "arrow-string", + "arrow-string 48.0.1", +] + +[[package]] +name = "arrow" +version = "50.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa285343fba4d829d49985bdc541e3789cf6000ed0e84be7c039438df4a4e78c" +dependencies = [ + "arrow-arith 50.0.0", + "arrow-array 50.0.0", + "arrow-buffer 50.0.0", + "arrow-cast 50.0.0", + "arrow-data 50.0.0", + "arrow-ipc 50.0.0", + "arrow-ord 50.0.0", + "arrow-row 50.0.0", + "arrow-schema 50.0.0", + "arrow-select 50.0.0", + "arrow-string 50.0.0", ] [[package]] @@ -360,18 +410,33 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-arith" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7029a5b3efbeafbf4a12d12dc16b8f9e9bff20a410b8c25c5d28acc089e1043" +dependencies = [ + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "chrono", + "half 2.3.1", + "num", +] + [[package]] name = "arrow-array" version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6eaf89041fa5937940ae390294ece29e1db584f46d995608d6e5fe65a2e0e9b" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-buffer 48.0.1", "arrow-data 48.0.1", "arrow-schema 48.0.1", "chrono", - "chrono-tz", + "chrono-tz 0.8.6", "half 2.3.1", "hashbrown 0.14.3", "num", @@ -383,7 +448,7 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d390feeb7f21b78ec997a4081a025baef1e2e0d6069e181939b61864c9779609" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-buffer 50.0.0", "arrow-data 50.0.0", "arrow-schema 50.0.0", @@ -393,6 +458,22 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-array" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d33238427c60271710695f17742f45b1a5dc5bcfc5c15331c25ddfe7abf70d97" +dependencies = [ + "ahash 0.8.11", + "arrow-buffer 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "chrono", + "half 2.3.1", + "hashbrown 0.14.3", + "num", +] + [[package]] name = "arrow-buffer" version = "48.0.1" @@ -415,6 +496,17 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-buffer" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9b95e825ae838efaf77e366c00d3fc8cca78134c9db497d6bda425f2e7b7c1" +dependencies = [ + "bytes", + "half 2.3.1", + "num", +] + [[package]] name = "arrow-cast" version = "48.0.1" @@ -451,6 +543,26 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-cast" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf8385a9d5b5fcde771661dd07652b79b9139fea66193eda6a88664400ccab" +dependencies = [ + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "arrow-select 52.0.0", + "atoi", + "base64 0.22.0", + "chrono", + "half 2.3.1", + "lexical-core", + "num", + "ryu", +] + [[package]] name = "arrow-csv" version = "48.0.1" @@ -494,6 +606,18 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-data" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb29be98f987bcf217b070512bb7afba2f65180858bca462edf4a39d84a23e10" +dependencies = [ + "arrow-buffer 52.0.0", + "arrow-schema 52.0.0", + "half 2.3.1", + "num", +] + [[package]] name = "arrow-flight" version = "50.0.0" @@ -525,7 +649,7 @@ dependencies = [ "arrow-cast 48.0.1", "arrow-data 48.0.1", "arrow-schema 48.0.1", - "flatbuffers", + "flatbuffers 23.5.26", ] [[package]] @@ -539,7 +663,21 @@ dependencies = [ "arrow-cast 50.0.0", "arrow-data 50.0.0", "arrow-schema 50.0.0", - "flatbuffers", + "flatbuffers 23.5.26", +] + +[[package]] +name = "arrow-ipc" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc68f6523970aa6f7ce1dc9a33a7d9284cfb9af77d4ad3e617dbe5d79cc6ec8" +dependencies = [ + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-cast 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "flatbuffers 24.3.25", ] [[package]] @@ -555,7 +693,7 @@ dependencies = [ "arrow-schema 48.0.1", "chrono", "half 2.3.1", - "indexmap 2.0.0", + "indexmap 2.2.6", "lexical-core", "num", "serde", @@ -592,13 +730,28 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-ord" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb56ed1547004e12203652f12fe12e824161ff9d1e5cf2a7dc4ff02ba94f413" +dependencies = [ + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "arrow-select 52.0.0", + "half 2.3.1", + "num", +] + [[package]] name = "arrow-row" version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d664318bc05f930559fc088888f0f7174d3c5bc888c0f4f9ae8f23aa398ba3" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-data 48.0.1", @@ -613,7 +766,7 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007035e17ae09c4e8993e4cb8b5b96edf0afb927cd38e2dff27189b274d83dcf" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 50.0.0", "arrow-buffer 50.0.0", "arrow-data 50.0.0", @@ -622,6 +775,21 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "arrow-row" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575b42f1fc588f2da6977b94a5ca565459f5ab07b60545e17243fb9a7ed6d43e" +dependencies = [ + "ahash 0.8.11", + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "half 2.3.1", + "hashbrown 0.14.3", +] + [[package]] name = "arrow-schema" version = "48.0.1" @@ -637,13 +805,19 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ff3e9c01f7cd169379d269f926892d0e622a704960350d09d331be3ec9e0029" +[[package]] +name = "arrow-schema" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32aae6a60458a2389c0da89c9de0b7932427776127da1a738e2efc21d32f3393" + [[package]] name = "arrow-select" version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374c4c3b812ecc2118727b892252a4a4308f87a8aca1dbf09f3ce4bc578e668a" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-data 48.0.1", @@ -657,7 +831,7 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce20973c1912de6514348e064829e50947e35977bb9d7fb637dc99ea9ffd78c" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 50.0.0", "arrow-buffer 50.0.0", "arrow-data 50.0.0", @@ -665,6 +839,20 @@ dependencies = [ "num", ] +[[package]] +name = "arrow-select" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de36abaef8767b4220d7b4a8c2fe5ffc78b47db81b03d77e2136091c3ba39102" +dependencies = [ + "ahash 0.8.11", + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "num", +] + [[package]] name = "arrow-string" version = "48.0.1" @@ -681,23 +869,74 @@ dependencies = [ "regex-syntax 0.8.2", ] +[[package]] +name = "arrow-string" +version = "50.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f3b37f2aeece31a2636d1b037dabb69ef590e03bdc7eb68519b51ec86932a7" +dependencies = [ + "arrow-array 50.0.0", + "arrow-buffer 50.0.0", + "arrow-data 50.0.0", + "arrow-schema 50.0.0", + "arrow-select 50.0.0", + "num", + "regex", + "regex-syntax 0.8.2", +] + +[[package]] +name = "arrow-string" +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e435ada8409bcafc910bc3e0077f532a4daa20e99060a496685c0e3e53cc2597" +dependencies = [ + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-data 52.0.0", + "arrow-schema 52.0.0", + "arrow-select 52.0.0", + "memchr", + "num", + "regex", + "regex-syntax 0.8.2", +] + +[[package]] +name = "arrow-udf-flight" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4adb3a066bd22fb520bc3d040d9d59ee54f320c21faeb6df815ea20445c80c54" +dependencies = [ + "arrow-array 50.0.0", + "arrow-flight", + "arrow-schema 50.0.0", + "arrow-select 50.0.0", + "futures-util", + "thiserror", + "tokio", + "tonic 0.10.2", + "tracing", +] + [[package]] name = "arrow-udf-js" -version = "0.1.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252b6355ad1e57eb6454b705c51652de55aa22eb018cdb95be0dbf62ee3ec78f" +checksum = "76cb6d108605c5489fff1ef9c520656946ad05ed0de3ea6d26d56bcb34bdb8c5" dependencies = [ "anyhow", "arrow-array 50.0.0", "arrow-buffer 50.0.0", "arrow-schema 50.0.0", + "atomic-time", "rquickjs", ] [[package]] name = "arrow-udf-js-deno" version = "0.0.1" -source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=23fe0dd#23fe0dd41616f4646f9139e22a335518e6cc9a47" +source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=fa36365#fa3636559de986aa592da6e8b3fbfac7bdd4bb78" dependencies = [ "anyhow", "arrow-array 50.0.0", @@ -719,7 +958,7 @@ dependencies = [ [[package]] name = "arrow-udf-js-deno-runtime" version = "0.0.1" -source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=23fe0dd#23fe0dd41616f4646f9139e22a335518e6cc9a47" +source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=fa36365#fa3636559de986aa592da6e8b3fbfac7bdd4bb78" dependencies = [ "anyhow", "deno_ast", @@ -746,8 +985,9 @@ dependencies = [ [[package]] name = "arrow-udf-python" -version = "0.1.0" -source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=6c32f71#6c32f710b5948147f8214797fc334a4a3cadef0d" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4506efc6fbc200c083add2a7ed4e3616a859941a745e922320ae7051d90d12ec" dependencies = [ "anyhow", "arrow-array 50.0.0", @@ -761,9 +1001,9 @@ dependencies = [ [[package]] name = "arrow-udf-wasm" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a51355b8ca4de8ae028e5efb45c248dad4568cde6707f23b89f9b86a907f36" +checksum = "eb829e25925161d93617d4b053bae03fe51e708f2cce088d85df856011d4f369" dependencies = [ "anyhow", "arrow-array 50.0.0", @@ -793,7 +1033,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -813,17 +1053,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.2.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-compression" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" dependencies = [ - "brotli", + "brotli 3.5.0", "bzip2", "flate2", "futures-core", @@ -856,7 +1109,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", "async-io", "async-lock", @@ -892,14 +1145,14 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", ] [[package]] name = "async-nats" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea7b126ebfa4db78e9e788b2a792b6329f35b4f2fdd56dbc646dedc2beec7a5" +checksum = "d5e47d2f7305524258908449aff6c86db36697a9b4219bfb1777e0ca1945358d" dependencies = [ "base64 0.22.0", "bytes", @@ -922,7 +1175,7 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tracing", "tryhard", "url", @@ -936,7 +1189,7 @@ checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -946,7 +1199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", - "async-channel", + "async-channel 1.9.0", "async-global-executor", "async-io", "async-lock", @@ -985,7 +1238,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -1002,7 +1255,20 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", +] + +[[package]] +name = "asynchronous-codec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", ] [[package]] @@ -1020,6 +1286,15 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" +[[package]] +name = "atomic-time" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3424654267706036b8c23c0abadc4e0412416b9d0208d7ebe1e6978c8c31fec0" +dependencies = [ + "portable-atomic", +] + [[package]] name = "atomic-waker" version = "1.1.1" @@ -1035,7 +1310,7 @@ dependencies = [ "derive_utils", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -1046,7 +1321,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -1110,9 +1385,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d67c6836a1009b23e3f4cd1457c83e0aa49a490d9c3033b53c3f7b8cf2facc0f" +checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -1162,6 +1437,24 @@ dependencies = [ "paste", ] +[[package]] +name = "aws-msk-iam-sasl-signer" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7036b8409ffe698dfdc5ae08722999d960092aeb738026ea99c3071c94831668" +dependencies = [ + "aws-config", + "aws-credential-types", + "aws-sdk-sts", + "aws-sigv4", + "aws-types", + "base64 0.22.0", + "chrono", + "futures", + "thiserror", + "url", +] + [[package]] name = "aws-runtime" version = "1.0.1" @@ -1184,6 +1477,29 @@ dependencies = [ "uuid", ] +[[package]] +name = "aws-sdk-dynamodb" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c4ed3708df2778c0c49b16e8235e52eb8f2133ae6752c40eea1376e2563fec" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand 2.0.1", + "http 0.2.9", + "regex", + "tracing", +] + [[package]] name = "aws-sdk-kinesis" version = "1.3.0" @@ -1289,9 +1605,9 @@ dependencies = [ [[package]] name = "aws-smithy-async" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eac0bb78e9e2765699999a02d7bfb4e6ad8f13e0962ebb9f5202b1d8cd76006" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" dependencies = [ "futures-util", "pin-project-lite", @@ -1390,31 +1706,33 @@ dependencies = [ "once_cell", "pin-project-lite", "pin-utils", - "rustls 0.21.10", + "rustls 0.21.11", "tokio", "tracing", ] [[package]] name = "aws-smithy-runtime-api" -version = "1.1.3" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02ca2da7619517310bfead6d18abcdde90f1439224d887d608503cfacff46dff" +checksum = "4179bd8a1c943e1aceb46c5b9fc014a561bd6c35a2153e816ba29076ee49d245" dependencies = [ "aws-smithy-async", "aws-smithy-types", "bytes", "http 0.2.9", + "http 1.1.0", "pin-project-lite", "tokio", "tracing", + "zeroize", ] [[package]] name = "aws-smithy-types" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4bb944488536cd2fef43212d829bc7e9a8bfc4afa079d21170441e7be8d2d0" +checksum = "cfe321a6b21f5d8eabd0ade9c55d3d0335f3c3157fc2b3e87f05f34b539e4df5" dependencies = [ "base64-simd 0.8.0", "bytes", @@ -1455,9 +1773,9 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.1.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2739d97d47f47cdf0d27982019a405dcc736df25925d1a75049f1faa79df88" +checksum = "02fa328e19c849b20ef7ada4c9b581dd12351ff35ecc7642d06e69de4f98407c" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -1506,7 +1824,7 @@ dependencies = [ "axum-core 0.4.3", "bytes", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "hyper 1.1.0", @@ -1556,7 +1874,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "mime", @@ -1576,7 +1894,7 @@ checksum = "077959a7f8cf438676af90b483304528eb7e16eadadb7f44e9ada4f9dceb9e62" dependencies = [ "axum-core 0.4.3", "chrono", - "http 1.0.0", + "http 1.1.0", "mime_guess", "rust-embed", "tower-service", @@ -1605,7 +1923,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.32.1", "rustc-demangle", ] @@ -1673,11 +1991,11 @@ dependencies = [ [[package]] name = "base64-url" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5b0a88aa36e9f095ee2e2b13fb8c5e4313e022783aedacc123328c0084916d" +checksum = "38e2b6c78c06f7288d5e3c3d683bde35a79531127c83b087e5d0d77c974b4b28" dependencies = [ - "base64 0.21.7", + "base64 0.22.0", ] [[package]] @@ -1728,17 +2046,24 @@ dependencies = [ [[package]] name = "bigdecimal" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06619be423ea5bb86c95f087d5707942791a08a85530df0db2209a3ecfb8bc9" +checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" dependencies = [ "autocfg", "libm", "num-bigint", "num-integer", "num-traits", + "serde", ] +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bincode" version = "1.3.3" @@ -1786,7 +2111,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.57", + "syn 2.0.66", "which 4.4.2", ] @@ -1820,12 +2145,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitmaps" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" - [[package]] name = "bitvec" version = "1.0.1" @@ -1894,7 +2213,7 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-lock", "async-task", "atomic-waker", @@ -1905,9 +2224,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf617fabf5cdbdc92f774bfe5062d870f228b80056d41180797abf48bed4056e" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ "borsh-derive", "cfg_aliases", @@ -1915,39 +2234,80 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f404657a7ea7b5249e36808dff544bc88a28f26e0ac40009f674b7a009d14be3" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", "syn_derive", ] [[package]] name = "brotli" -version = "3.3.4" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", - "brotli-decompressor", + "brotli-decompressor 2.5.1", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 4.0.1", ] [[package]] name = "brotli-decompressor" -version = "2.3.4" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bson" +version = "2.11.0" +source = "git+https://github.com/risingwavelabs/bson-rust?rev=e5175ec#e5175ecfe3bebaaf58df543a1ae2bf868e476052" +dependencies = [ + "ahash 0.8.11", + "base64 0.13.1", + "bitvec", + "hex", + "indexmap 2.2.6", + "js-sys", + "once_cell", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + [[package]] name = "bstr" version = "1.6.2" @@ -2061,6 +2421,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cache-padded" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" + [[package]] name = "cache_control" version = "0.2.0" @@ -2223,20 +2589,20 @@ checksum = "bc7cb2538d4ecc42b6c3b57a83094d8c69894e74468d18cd045a09fdea807358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "cfg_aliases" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -2244,26 +2610,48 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] name = "chrono-tz" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1369bc6b9e9a7dfdae2055f6ec151fe9c554a9d23d357c0237cee2e25eaabb7" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" dependencies = [ "chrono", - "chrono-tz-build", + "chrono-tz-build 0.2.1", + "phf", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build 0.3.0", "phf", "uncased", ] [[package]] name = "chrono-tz-build" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2f5ebdc942f57ed96d560a6d1a459bae5851102a25d5bf89dc04ae453e31ecf" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" dependencies = [ "parse-zoneinfo", "phf", @@ -2338,7 +2726,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim 0.11.1", ] [[package]] @@ -2350,7 +2738,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -2372,7 +2760,7 @@ dependencies = [ "hyper 0.14.27", "hyper-tls 0.5.0", "lz4", - "sealed", + "sealed 0.4.0", "serde", "static_assertions", "thiserror", @@ -2402,21 +2790,23 @@ dependencies = [ ] [[package]] -name = "cmake" -version = "0.1.50" +name = "cmac" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" dependencies = [ - "cc", + "cipher", + "dbl", + "digest", ] [[package]] -name = "cmsketch" -version = "0.1.5" +name = "cmake" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93710598b87c37ea250ab17a36f9f79dbaf3bd20e55806cf09345103bc26d60e" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ - "paste", + "cc", ] [[package]] @@ -2491,6 +2881,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "connection-string" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510ca239cf13b7f8d16a2b48f263de7b4f8c566f0af58d901031473c76afb1e3" + [[package]] name = "console" version = "0.15.7" @@ -2506,22 +2902,22 @@ dependencies = [ [[package]] name = "console-api" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" +checksum = "a257c22cd7e487dd4a13d413beabc512c5052f0bc048db0da6a84c3d8a6142fd" dependencies = [ "futures-core", "prost 0.12.1", "prost-types 0.12.1", - "tonic 0.10.2", + "tonic 0.11.0", "tracing-core", ] [[package]] name = "console-subscriber" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" +checksum = "31c4cc54bae66f7d9188996404abdf7fdfa23034ef8e43478c8810828abad758" dependencies = [ "console-api", "crossbeam-channel", @@ -2529,13 +2925,14 @@ dependencies = [ "futures-task", "hdrhistogram", "humantime", + "prost 0.12.1", "prost-types 0.12.1", "serde", "serde_json", "thread_local", "tokio", "tokio-stream", - "tonic 0.10.2", + "tonic 0.11.0", "tracing", "tracing-core", "tracing-subscriber", @@ -2548,11 +2945,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a16ff120e0b2d07fdcfda1cc2c1c72b76d3b2fe7cc5ec82bf7b42769b2e73c" dependencies = [ "auto_enums", - "darling 0.20.8", + "darling 0.20.9", "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -2563,23 +2960,21 @@ checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "const-random" -version = "0.1.15" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", - "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", - "proc-macro-hack", "tiny-keccak", ] @@ -2661,18 +3056,18 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3775cc6cc00c90d29eebea55feedb2b0168e23f5415bab7859c4004d7323d1" +checksum = "79b27922a6879b5b5361d0a084cb0b1941bf109a98540addcb932da13b68bed4" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637f3184ba5bfa48d425bad1d2e4faf5fcf619f5e0ca107edc6dc02f589d4d74" +checksum = "304c455b28bf56372729acb356afbb55d622f2b0f2f7837aa5e57c138acaac4d" dependencies = [ "bumpalo", "cranelift-bforest", @@ -2691,33 +3086,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b35b8240462341d94d31aab807cad704683988708261aecae3d57db48b7212" +checksum = "1653c56b99591d07f67c5ca7f9f25888948af3f4b97186bff838d687d666f613" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3cd1555aa9df1d6d8375732de41b4cb0d787006948d55b6d004d521e9efeb0" +checksum = "f5b6a9cf6b6eb820ee3f973a0db313c05dc12d370f37b4fe9630286e1672573f" [[package]] name = "cranelift-control" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b31a562a10e98ab148fa146801e20665c5f9eda4fce9b2c5a3836575887d74" +checksum = "d9d06e6bf30075fb6bed9e034ec046475093392eea1aff90eb5c44c4a033d19a" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1e0467700a3f4fccf5feddbaebdf8b0eb82535b06a9600c4bc5df40872e75d" +checksum = "29be04f931b73cdb9694874a295027471817f26f26d2f0ebe5454153176b6e3a" dependencies = [ "serde", "serde_derive", @@ -2725,9 +3120,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb918ee2c23939262efd1b99d76a21212ac7bd35129582133e21a22a6ff0467" +checksum = "a07fd7393041d7faa2f37426f5dc7fc04003b70988810e8c063beefeff1cd8f9" dependencies = [ "cranelift-codegen", "log", @@ -2737,15 +3132,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966e4cfb23cf6d7f1d285d53a912baaffc5f06bcd9c9b0a2d8c66a184fae534b" +checksum = "f341d7938caa6dff8149dac05bb2b53fc680323826b83b4cf175ab9f5139a3c9" [[package]] name = "cranelift-native" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea803aadfc4aabdfae7c3870f1b1f6dd4332f4091859e9758ef5fca6bf8cc87" +checksum = "82af6066e6448d26eeabb7aa26a43f7ff79f8217b06bade4ee6ef230aecc8880" dependencies = [ "cranelift-codegen", "libc", @@ -2754,9 +3149,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d18a3572cd897555bba3621e568029417d8f5cc26aeede2d7cb0bad6afd916" +checksum = "2766fab7284a914a7f17f90ebe865c86453225fb8637ac31f123f5028fee69cd" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -2791,9 +3186,9 @@ checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" [[package]] name = "crc32c" -version = "0.6.4" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f48d60e5b4d2c53d5c2b1d8a58c849a70ae5e5509b08a48d047e3b65714a74" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" dependencies = [ "rustc_version 0.4.0", ] @@ -2817,7 +3212,7 @@ dependencies = [ "proc-macro-error 1.0.4", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -3025,6 +3420,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "ctr" version = "0.9.2" @@ -3036,9 +3441,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -3059,7 +3464,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -3086,7 +3491,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -3103,7 +3508,7 @@ checksum = "2fa16a70dd58129e4dfffdff535fb1bce66673f7bbeec4a5a1765a504e1ccd84" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -3128,12 +3533,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ - "darling_core 0.20.8", - "darling_macro 0.20.8", + "darling_core 0.20.9", + "darling_macro 0.20.9", ] [[package]] @@ -3166,16 +3571,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 2.0.57", + "strsim 0.11.1", + "syn 2.0.66", ] [[package]] @@ -3202,13 +3607,13 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ - "darling_core 0.20.8", + "darling_core 0.20.9", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -3263,8 +3668,8 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "676796427e638d85e9eadf13765705212be60b34f8fc5d3934d95184c63ca1b4" dependencies = [ - "ahash 0.8.6", - "arrow", + "ahash 0.8.11", + "arrow 48.0.1", "arrow-array 48.0.1", "arrow-schema 48.0.1", "async-compression", @@ -3285,7 +3690,7 @@ dependencies = [ "glob", "half 2.3.1", "hashbrown 0.14.3", - "indexmap 2.0.0", + "indexmap 2.2.6", "itertools 0.11.0", "log", "num_cpus", @@ -3310,8 +3715,8 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e23b3d21a6531259d291bd20ce59282ea794bda1018b0a1e278c13cd52e50c" dependencies = [ - "ahash 0.8.6", - "arrow", + "ahash 0.8.11", + "arrow 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-schema 48.0.1", @@ -3329,7 +3734,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4de1fd0d8db0f2b8e4f4121bfa1c7c09d3a5c08a0a65c2229cd849eb65cff855" dependencies = [ - "arrow", + "arrow 48.0.1", "chrono", "dashmap", "datafusion-common", @@ -3350,8 +3755,8 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e227fe88bf6730cab378d0cd8fc4c6b2ea42bc7e414a8ea9feba7225932735" dependencies = [ - "ahash 0.8.6", - "arrow", + "ahash 0.8.11", + "arrow 48.0.1", "arrow-array 48.0.1", "datafusion-common", "sqlparser", @@ -3365,7 +3770,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6648e62ea7605b9bfcd87fdc9d67e579c3b9ac563a87734ae5fe6d79ee4547" dependencies = [ - "arrow", + "arrow 48.0.1", "async-trait", "chrono", "datafusion-common", @@ -3383,8 +3788,8 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f32b8574add16a32411a9b3fb3844ac1fc09ab4e7be289f86fd56d620e4f2508" dependencies = [ - "ahash 0.8.6", - "arrow", + "ahash 0.8.11", + "arrow 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-ord 48.0.1", @@ -3398,7 +3803,7 @@ dependencies = [ "half 2.3.1", "hashbrown 0.14.3", "hex", - "indexmap 2.0.0", + "indexmap 2.2.6", "itertools 0.11.0", "libc", "log", @@ -3418,8 +3823,8 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "796abd77d5bfecd9e5275a99daf0ec45f5b3a793ec431349ce8211a67826fd22" dependencies = [ - "ahash 0.8.6", - "arrow", + "ahash 0.8.11", + "arrow 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-schema 48.0.1", @@ -3432,7 +3837,7 @@ dependencies = [ "futures", "half 2.3.1", "hashbrown 0.14.3", - "indexmap 2.0.0", + "indexmap 2.2.6", "itertools 0.11.0", "log", "once_cell", @@ -3449,7 +3854,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26de2592417beb20f73f29b131a04d7de14e2a6336c631554d611584b4306236" dependencies = [ - "arrow", + "arrow 48.0.1", "chrono", "datafusion", "datafusion-common", @@ -3464,7 +3869,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced70b8a5648ba7b95c61fc512183c33287ffe2c9f22ffe22700619d7d48c84f" dependencies = [ - "arrow", + "arrow 48.0.1", "arrow-schema 48.0.1", "datafusion-common", "datafusion-expr", @@ -3490,6 +3895,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array", +] + [[package]] name = "debugid" version = "0.8.0" @@ -3502,9 +3916,9 @@ dependencies = [ [[package]] name = "delta_btree_map" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ - "educe 0.5.7", + "educe", "enum-as-inner 0.6.0", ] @@ -3521,7 +3935,7 @@ name = "deltalake-core" version = "0.17.0" source = "git+https://github.com/risingwavelabs/delta-rs?rev=5c2dccd4640490202ffe98adbd13b09cef8e007b#5c2dccd4640490202ffe98adbd13b09cef8e007b" dependencies = [ - "arrow", + "arrow 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-cast 48.0.1", @@ -3701,9 +4115,9 @@ dependencies = [ "deno_core", "deno_tls", "dyn-clone", - "http 1.0.0", + "http 1.1.0", "pin-project", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde", "serde_json", "tokio", @@ -3718,14 +4132,14 @@ dependencies = [ "async-compression", "async-trait", "base64 0.21.7", - "brotli", + "brotli 3.5.0", "bytes", "cache_control", "deno_core", "deno_net", "deno_websocket", "flate2", - "http 1.0.0", + "http 1.1.0", "httparse", "hyper 0.14.27", "hyper 1.1.0", @@ -3815,7 +4229,7 @@ dependencies = [ "quote", "strum 0.25.0", "strum_macros 0.25.3", - "syn 2.0.57", + "syn 2.0.66", "thiserror", ] @@ -3837,7 +4251,7 @@ dependencies = [ "deno_core", "deno_native_certs", "once_cell", - "rustls 0.22.3", + "rustls 0.22.4", "rustls-pemfile 1.0.4", "rustls-tokio-stream", "rustls-webpki 0.101.7", @@ -3903,7 +4317,7 @@ dependencies = [ "deno_tls", "fastwebsockets", "h2 0.4.4", - "http 1.0.0", + "http 1.1.0", "http-body-util", "hyper 1.1.0", "hyper-util", @@ -3991,10 +4405,10 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" dependencies = [ - "darling 0.20.8", + "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4014,7 +4428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core 0.20.0", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4038,7 +4452,7 @@ checksum = "61bb5a1014ce6dfc2a378578509abe775a5aa06bff584a547555d9efdb81b926" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4156,7 +4570,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4227,16 +4641,16 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "duration-str" -version = "0.7.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8bb6a301a95ba86fa0ebaf71d49ae4838c51f8b84cb88ed140dfb66452bb3c4" +checksum = "709d653e7c92498eb29fb86a2a6f0f3502b97530f33aedb32ef848d4d28b31a3" dependencies = [ "chrono", - "nom", "rust_decimal", "serde", "thiserror", "time", + "winnow 0.6.11", ] [[package]] @@ -4304,33 +4718,21 @@ dependencies = [ [[package]] name = "educe" -version = "0.4.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" -dependencies = [ - "enum-ordinalize 3.1.13", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "educe" -version = "0.5.7" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597b6c9e278fefec3b25dfbb86195aeafe270dc6f13e27a3a20a95ba449f1bfb" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" dependencies = [ - "enum-ordinalize 4.3.0", + "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" dependencies = [ "serde", ] @@ -4382,6 +4784,70 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -4391,6 +4857,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "enum-as-inner" version = "0.5.1" @@ -4412,7 +4890,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4437,35 +4915,22 @@ dependencies = [ [[package]] name = "enum-iterator" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600536cfe9e2da0820aa498e570f6b2b9223eec3ce2f835c8ae4861304fa4794" +checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.57", -] - -[[package]] -name = "enum-ordinalize" -version = "3.1.13" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4f76552f53cefc9a7f64987c3701b99d982f7690606fd67de1d09712fbf52f1" +checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b" dependencies = [ - "num-bigint", - "num-traits", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4485,7 +4950,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4505,7 +4970,7 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -4596,6 +5061,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +dependencies = [ + "event-listener 5.2.0", + "pin-project-lite", +] + [[package]] name = "expect-test" version = "1.5.0" @@ -4617,6 +5103,28 @@ dependencies = [ "rand", ] +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "synstructure", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -4807,6 +5315,16 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "flatbuffers" +version = "24.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8add37afff2d4ffa83bc748a70b4b1370984f6980768554182424ef71447c35f" +dependencies = [ + "bitflags 1.3.2", + "rustc_version 0.4.0", +] + [[package]] name = "flate2" version = "1.0.27" @@ -4891,130 +5409,111 @@ dependencies = [ [[package]] name = "foyer" -version = "0.6.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15240094bab62dfeb59bb974e2fb7bff18727c6dc7bd1f5f6fc82a9e6fda5a38" +checksum = "cbfceb28a004d50b6110fd012db1d52d318d15f721dbb5b65eda261b821c1baa" dependencies = [ + "ahash 0.8.11", + "anyhow", "foyer-common", - "foyer-intrusive", "foyer-memory", "foyer-storage", - "foyer-workspace-hack", + "futures", + "madsim-tokio", + "minitrace", + "pin-project", + "tracing", ] [[package]] name = "foyer-common" -version = "0.4.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad95f985cfbd5a8bf9f115d963918e742dfbb75350febff509b09cf194842b0c" +checksum = "6ce1b903e1e142eafe8cd3183087fcd7ed6452e5dc8dfb5356a607ec2aa1c869" dependencies = [ - "anyhow", "bytes", - "foyer-workspace-hack", - "itertools 0.12.1", + "cfg-if", + "crossbeam", + "futures", + "hashbrown 0.14.3", + "itertools 0.13.0", "madsim-tokio", + "metrics", + "minitrace", + "nix 0.29.0", "parking_lot 0.12.1", - "paste", - "tracing", + "pin-project", + "rustversion", + "serde", ] [[package]] name = "foyer-intrusive" -version = "0.3.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf22ed0dfa6315714099046504711d5563d191a442afcd87bd4d0bf5010bb9a" +checksum = "2f7883ac7a8a69115f5bd84072a0b98d4ba72feacddf9040c217a5012bb352ff" dependencies = [ - "bytes", - "cmsketch 0.1.5", "foyer-common", - "foyer-workspace-hack", - "itertools 0.12.1", - "memoffset", - "parking_lot 0.12.1", - "paste", - "tracing", - "twox-hash", + "itertools 0.13.0", ] [[package]] name = "foyer-memory" -version = "0.1.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59727e86709e4eab603f8a5086f3b2c7d3ddddf7c8b31d8dea2b00364b7fb95" +checksum = "9fd7b0ce80867803c6b197db20cc1a49fcecc9d5070d2fb829660ec19acf9e72" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "bitflags 2.5.0", - "cmsketch 0.2.0", - "crossbeam", + "cmsketch", + "foyer-common", "foyer-intrusive", - "foyer-workspace-hack", "futures", "hashbrown 0.14.3", - "itertools 0.12.1", + "itertools 0.13.0", "libc", "madsim-tokio", + "minitrace", "parking_lot 0.12.1", + "pin-project", + "serde", + "tracing", ] [[package]] name = "foyer-storage" -version = "0.5.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e0749c569ef5f8d9f877a6a81da24e7d1b6f8ea87ec6c3933e12dac0c7632f" +checksum = "4205359a7ea9d05ff7525f0e55a5257a593c89b4e22ebfa07e06a84c2e163202" dependencies = [ + "ahash 0.8.11", + "allocator-api2", "anyhow", + "array-util", + "async-channel 2.2.1", + "bincode 1.3.3", "bitflags 2.5.0", - "bitmaps", "bytes", + "either", "foyer-common", - "foyer-intrusive", - "foyer-workspace-hack", + "foyer-memory", "futures", - "itertools 0.12.1", + "itertools 0.13.0", + "lazy_static", "libc", "lz4", "madsim-tokio", - "memoffset", - "nix 0.28.0", + "minitrace", "parking_lot 0.12.1", - "paste", - "prometheus", + "pin-project", "rand", + "serde", "thiserror", "tracing", "twox-hash", "zstd 0.13.0", ] -[[package]] -name = "foyer-workspace-hack" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5ee5ab890d1893232fd44af33197c7499813816b8880649a15ee9438d5e07" -dependencies = [ - "ahash 0.8.6", - "cc", - "crossbeam-channel", - "crossbeam-utils", - "either", - "futures-channel", - "futures-core", - "futures-executor", - "futures-sink", - "futures-util", - "hashbrown 0.14.3", - "libc", - "parking_lot 0.12.1", - "parking_lot_core 0.9.8", - "proc-macro2", - "quote", - "rand", - "smallvec", - "syn 2.0.57", - "tokio", - "tracing-core", -] - [[package]] name = "fragile" version = "2.0.0" @@ -5029,7 +5528,7 @@ checksum = "3a0b11eeb173ce52f84ebd943d42e58813a2ebb78a6a3ff0a243b71c5199cd7b" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -5057,7 +5556,7 @@ checksum = "b0fa992f1656e1707946bbba340ad244f0814009ef8c0118eb7b658395f19a2e" dependencies = [ "frunk_proc_macro_helpers", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -5069,7 +5568,7 @@ dependencies = [ "frunk_core", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -5081,7 +5580,7 @@ dependencies = [ "frunk_core", "frunk_proc_macro_helpers", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -5196,7 +5695,7 @@ checksum = "5df2c13d48c8cb8a3ec093ede6f0f4482f327d7bb781120c5fb483ef0f17e758" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -5266,7 +5765,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -5431,15 +5930,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" dependencies = [ "fallible-iterator 0.3.0", - "indexmap 2.0.0", + "indexmap 2.2.6", "stable_deref_trait", ] [[package]] name = "ginepro" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eedbff62a689be48f58f32571dbf3d60c4a73b39740141dfe7ac942536ea27f7" +checksum = "3b00ef897d4082727a53ea1111cd19bfa4ccdc476a5eb9f49087047113a43891" dependencies = [ "anyhow", "async-trait", @@ -5472,9 +5971,9 @@ dependencies = [ [[package]] name = "google-cloud-auth" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe54dd8b6eb2bcd5390998238bcc39d1daed4dbb70df2845832532540384fc41" +checksum = "e09ed5b2998bc8d0d3df09c859028210d4961b8fe779cfda8dc8ca4e83d5def2" dependencies = [ "async-trait", "base64 0.21.7", @@ -5482,7 +5981,7 @@ dependencies = [ "google-cloud-token", "home", "jsonwebtoken", - "reqwest 0.11.20", + "reqwest 0.12.4", "serde", "serde_json", "thiserror", @@ -5492,6 +5991,33 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "google-cloud-bigquery" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e321c127945bb44a5cf5129c37530e2494b97afefe7f334a983ac754e40914e" +dependencies = [ + "anyhow", + "arrow 50.0.0", + "async-trait", + "backon", + "base64 0.21.7", + "bigdecimal 0.4.5", + "google-cloud-auth", + "google-cloud-gax", + "google-cloud-googleapis", + "google-cloud-token", + "num-bigint", + "reqwest 0.12.4", + "reqwest-middleware", + "serde", + "serde_json", + "thiserror", + "time", + "tokio", + "tracing", +] + [[package]] name = "google-cloud-gax" version = "0.17.0" @@ -5510,9 +6036,9 @@ dependencies = [ [[package]] name = "google-cloud-googleapis" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8a478015d079296167e3f08e096dc99cffc2cb50fa203dd38aaa9dd37f8354" +checksum = "32cd184c52aa2619ac1b16ad8b5a752e91d25be88a8cf08eaec19777dfacbe54" dependencies = [ "prost 0.12.1", "prost-types 0.12.1", @@ -5521,22 +6047,22 @@ dependencies = [ [[package]] name = "google-cloud-metadata" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc279bfb50487d7bcd900e8688406475fc750fe474a835b2ab9ade9eb1fc90e2" +checksum = "04f945a208886a13d07636f38fb978da371d0abc3e34bad338124b9f8c135a8f" dependencies = [ - "reqwest 0.11.20", + "reqwest 0.12.4", "thiserror", "tokio", ] [[package]] name = "google-cloud-pubsub" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2184a5c70b994e6d77eb1c140e193e7f5fe6015e9115322fac24f7e33f003c" +checksum = "0a35e4a008db5cf01a5c03d3c67bd90b3cad77427ca949f3c8eddd90c4a3c932" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-stream", "google-cloud-auth", "google-cloud-gax", @@ -5618,7 +6144,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.9", - "indexmap 2.0.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -5636,8 +6162,8 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http 1.0.0", - "indexmap 2.0.0", + "http 1.1.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -5677,7 +6203,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] @@ -5686,7 +6212,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", ] [[package]] @@ -5695,7 +6221,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "allocator-api2", ] @@ -5740,6 +6266,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.2" @@ -5828,9 +6360,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -5855,7 +6387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http 1.0.0", + "http 1.1.0", ] [[package]] @@ -5866,7 +6398,7 @@ checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", "futures-core", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "pin-project-lite", ] @@ -5929,7 +6461,7 @@ dependencies = [ "futures-channel", "futures-util", "h2 0.4.4", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "httparse", "httpdate", @@ -5949,7 +6481,7 @@ dependencies = [ "http 0.2.9", "hyper 0.14.27", "log", - "rustls 0.21.10", + "rustls 0.21.11", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", @@ -5963,10 +6495,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http 1.0.0", + "http 1.1.0", "hyper 1.1.0", "hyper-util", - "rustls 0.22.3", + "rustls 0.22.4", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", @@ -6023,7 +6555,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "hyper 1.1.0", "pin-project-lite", @@ -6062,29 +6594,95 @@ dependencies = [ ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "iceberg" +version = "0.2.0" +source = "git+https://github.com/risingwavelabs/iceberg-rust.git?rev=0c6e133e6f4655ff9ce4ad57b577dc7f692dd902#0c6e133e6f4655ff9ce4ad57b577dc7f692dd902" +dependencies = [ + "anyhow", + "apache-avro 0.17.0", + "array-init", + "arrow-arith 52.0.0", + "arrow-array 52.0.0", + "arrow-ord 52.0.0", + "arrow-schema 52.0.0", + "arrow-select 52.0.0", + "arrow-string 52.0.0", + "async-stream", + "async-trait", + "bimap", + "bitvec", + "bytes", + "chrono", + "derive_builder 0.20.0", + "either", + "fnv", + "futures", + "itertools 0.13.0", + "lazy_static", + "log", + "murmur3", + "once_cell", + "opendal", + "ordered-float 4.1.1", + "parquet 52.0.0", + "reqwest 0.12.4", + "rust_decimal", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "serde_repr", + "serde_with 3.8.0", + "tokio", + "typed-builder 0.18.2", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "iceberg-catalog-rest" +version = "0.2.0" +source = "git+https://github.com/risingwavelabs/iceberg-rust.git?rev=0c6e133e6f4655ff9ce4ad57b577dc7f692dd902#0c6e133e6f4655ff9ce4ad57b577dc7f692dd902" dependencies = [ - "cc", + "async-trait", + "chrono", + "iceberg", + "itertools 0.13.0", + "log", + "reqwest 0.12.4", + "serde", + "serde_derive", + "serde_json", + "typed-builder 0.18.2", + "urlencoding", + "uuid", ] [[package]] name = "icelake" version = "0.0.10" -source = "git+https://github.com/icelake-io/icelake?rev=54fd72fbd1dd8c592f05eeeb79223c8a6a33c297#54fd72fbd1dd8c592f05eeeb79223c8a6a33c297" +source = "git+https://github.com/icelake-io/icelake?rev=07d53893d7788b4e41fc11efad8a6be828405c31#07d53893d7788b4e41fc11efad8a6be828405c31" dependencies = [ "anyhow", "apache-avro 0.17.0", - "arrow-arith 50.0.0", - "arrow-array 50.0.0", - "arrow-buffer 50.0.0", - "arrow-cast 50.0.0", - "arrow-ord 50.0.0", - "arrow-row 50.0.0", - "arrow-schema 50.0.0", - "arrow-select 50.0.0", + "arrow-arith 52.0.0", + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-cast 52.0.0", + "arrow-ord 52.0.0", + "arrow-row 52.0.0", + "arrow-schema 52.0.0", + "arrow-select 52.0.0", "async-trait", "bitvec", "bytes", @@ -6101,7 +6699,7 @@ dependencies = [ "once_cell", "opendal", "ordered-float 3.9.1", - "parquet 50.0.0", + "parquet 52.0.0", "prometheus", "regex", "reqwest 0.11.20", @@ -6109,7 +6707,7 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "serde_with", + "serde_with 3.8.0", "tokio", "toml 0.7.8", "url", @@ -6179,9 +6777,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -6219,8 +6817,8 @@ version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73c0fefcb6d409a6587c07515951495d482006f89a21daa0f2f783aa4fd5e027" dependencies = [ - "ahash 0.8.6", - "indexmap 2.0.0", + "ahash 0.8.11", + "indexmap 2.2.6", "is-terminal", "itoa", "log", @@ -6239,7 +6837,7 @@ checksum = "ce243b1bfa62ffc028f1cc3b6034ec63d649f3031bc8a4fbbb004e1ac17d1f68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -6320,7 +6918,7 @@ dependencies = [ "socket2 0.5.6", "widestring", "windows-sys 0.48.0", - "winreg", + "winreg 0.50.0", ] [[package]] @@ -6338,7 +6936,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -6379,6 +6977,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -6459,9 +7066,9 @@ dependencies = [ [[package]] name = "jsonbb" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2edfc17ad42a5ece82df036301d5ef0c3dc3d071e28aa8a62e461711c55d19" +checksum = "91cdcbd02ee94c68803dd808bf8406e91491eaf875f09da650f5893dc56be18c" dependencies = [ "bytes", "serde", @@ -6623,15 +7230,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libflate" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7d5654ae1795afc7ff76f4365c2c8791b0feb18e8996a96adad8ffd7c3b2bf" +checksum = "45d9dfdc14ea4ef0900c1cddbc8dcd553fbaacd8a4a282cf4018ae9dd04fb21e" dependencies = [ "adler32", "core2", @@ -6642,12 +7249,12 @@ dependencies = [ [[package]] name = "libflate_lz77" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5f52fb8c451576ec6b79d3f4deb327398bc05bbdbd99021a6e77a4c855d524" +checksum = "e6e0d73b369f386f1c44abd9c570d5318f55ccde816ff4b562fa452e5182863d" dependencies = [ "core2", - "hashbrown 0.13.2", + "hashbrown 0.14.3", "rle-decode-fast", ] @@ -6689,17 +7296,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libtest-mimic" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d8de370f98a6cb8a4606618e53e802f93b094ddec0f96988eaec2c27e6e9ce7" -dependencies = [ - "clap", - "termcolor", - "threadpool", -] - [[package]] name = "libtest-mimic" version = "0.7.0" @@ -6756,7 +7352,7 @@ checksum = "04e542a18c94a9b6fcc7adb090fa3ba6b79ee220a16404f325672729f32a66ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -6779,7 +7375,7 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "local_stats_alloc" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "workspace-hack", ] @@ -6796,9 +7392,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "value-bag", ] @@ -6831,9 +7427,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +checksum = "d6eab492fe7f8651add23237ea56dbf11b3c4ff762ab83d40a47f11433421f91" dependencies = [ "libc", "lz4-sys", @@ -6841,9 +7437,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "e9764018d143cc854c9f17f0b907de70f14393b1f502da6375dce70f00514eb3" dependencies = [ "cc", "libc", @@ -6869,32 +7465,23 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "mach2" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] [[package]] name = "madsim" -version = "0.2.22" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7777a8bc4e68878b6e5433ac7b9bc196d9ccdfeef1f7cb3d23193cb997a520c9" +checksum = "f88753ddf8d3cd43b9cf71a93626dd9aad3c24086a04420beb31922e1f856d02" dependencies = [ - "ahash 0.7.7", - "async-channel", + "ahash 0.8.11", + "async-channel 2.2.1", "async-stream", "async-task", "bincode 1.3.3", @@ -6913,7 +7500,7 @@ dependencies = [ "spin 0.9.8", "tokio", "tokio-util", - "toml 0.7.8", + "toml 0.8.12", "tracing", "tracing-subscriber", ] @@ -6930,7 +7517,7 @@ dependencies = [ "aws-smithy-types", "aws-types", "bytes", - "http 1.0.0", + "http 1.1.0", "madsim", "spin 0.9.8", "tracing", @@ -6947,7 +7534,7 @@ dependencies = [ "http 0.2.9", "madsim", "serde", - "serde_with", + "serde_with 3.8.0", "spin 0.9.8", "thiserror", "tokio", @@ -6970,11 +7557,11 @@ dependencies = [ [[package]] name = "madsim-rdkafka" -version = "0.3.4+0.34.0" +version = "0.4.2+0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0992d46c19414bda24bad935ea223ad025dcd7e64f38f0acee388efa8ff5319b" +checksum = "6038ceb55ecf0d80eeae01a85dc9db052f0629ca2d82732923c022185096a76c" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-trait", "futures-channel", "futures-util", @@ -6994,9 +7581,9 @@ dependencies = [ [[package]] name = "madsim-tokio" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5611fd0eb96867dd03a9fd2494d4c1bb126f413519673195065b6ea011e8c68" +checksum = "569929c869275edc1e2c1f1381a688a6e5a4200302b58caff819e07414ccddb9" dependencies = [ "madsim", "spin 0.9.8", @@ -7029,7 +7616,7 @@ dependencies = [ "proc-macro2", "prost-build 0.12.1", "quote", - "syn 2.0.57", + "syn 2.0.66", "tonic-build", ] @@ -7138,6 +7725,45 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metrics" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" +dependencies = [ + "ahash 0.8.11", + "portable-atomic", +] + +[[package]] +name = "metrics-prometheus" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51557a875fdbd5b953b698ecd6cd06efef47618e02d95ad912e2392e5b5617ff" +dependencies = [ + "arc-swap", + "metrics", + "metrics-util", + "once_cell", + "prometheus", + "sealed 0.5.0", + "smallvec", + "thiserror", +] + +[[package]] +name = "metrics-util" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4259040465c955f9f2f1a4a8a16dc46726169bca0f88e8fb2dbeced487c3e828" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.14.3", + "metrics", + "num_cpus", +] + [[package]] name = "mime" version = "0.3.17" @@ -7160,6 +7786,46 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minitrace" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197d538cd69839d49a593c8c72df44291b0ea3296ecc0c85529002c53c8fbc6f" +dependencies = [ + "minitrace-macro", + "minstant", + "once_cell", + "parking_lot 0.12.1", + "pin-project", + "rand", + "rtrb", +] + +[[package]] +name = "minitrace-macro" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14efd4b574325fcb981bce1ac700b9ccf071ec2eb94f7a6a6b583a84f228ba47" +dependencies = [ + "proc-macro-error 1.0.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "minitrace-opentelemetry" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabb581e91af60aab44f6d49ee70646c87162ad25224cd6184ae67cfca08db05" +dependencies = [ + "futures", + "log", + "minitrace", + "opentelemetry", + "opentelemetry_sdk", +] + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -7169,6 +7835,16 @@ dependencies = [ "adler", ] +[[package]] +name = "minstant" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb9b5c752f145ac5046bccc3c4f62892e3c950c1d1eab80c5949cd68a2078db" +dependencies = [ + "ctor", + "web-time", +] + [[package]] name = "mio" version = "0.8.11" @@ -7238,6 +7914,53 @@ dependencies = [ "uuid", ] +[[package]] +name = "mongodb" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef206acb1b72389b49bc9985efe7eb1f8a9bb18e5680d262fac26c07f44025f1" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bitflags 1.3.2", + "bson", + "chrono", + "derivative", + "derive_more", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hmac", + "lazy_static", + "md-5", + "pbkdf2 0.11.0", + "percent-encoding", + "rand", + "rustc_version_runtime", + "rustls 0.21.11", + "rustls-pemfile 1.0.4", + "serde", + "serde_bytes", + "serde_with 1.14.0", + "sha-1", + "sha2", + "socket2 0.4.9", + "stringprep", + "strsim 0.10.0", + "take_mut", + "thiserror", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "trust-dns-proto 0.21.2", + "trust-dns-resolver 0.21.2", + "typed-builder 0.10.0", + "uuid", + "webpki-roots 0.25.2", +] + [[package]] name = "more-asserts" version = "0.3.1" @@ -7271,14 +7994,14 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c60492b5eb751e55b42d716b6b26dceb66767996cd7a5560a842fbf613ca2e92" dependencies = [ - "darling 0.20.8", + "darling 0.20.9", "heck 0.4.1", "num-bigint", "proc-macro-crate 3.1.0", "proc-macro-error 1.0.4", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", "termcolor", "thiserror", ] @@ -7324,7 +8047,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a60cb978c0a1d654edcc1460f8d6092dacf21346ed6017d81fb76a23ef5a8de" dependencies = [ "base64 0.21.7", - "bigdecimal 0.4.2", + "bigdecimal 0.4.5", "bindgen", "bitflags 2.5.0", "bitvec", @@ -7420,9 +8143,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -7527,11 +8250,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", "rand", @@ -7576,11 +8298,10 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] @@ -7609,9 +8330,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -7688,10 +8409,19 @@ name = "object" version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" dependencies = [ "crc32fast", "hashbrown 0.14.3", - "indexmap 2.0.0", + "indexmap 2.2.6", "memchr", ] @@ -7745,20 +8475,21 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "opendal" -version = "0.45.1" +version = "0.47.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c17c077f23fa2d2c25d9d22af98baa43b8bbe2ef0de80cf66339aa70401467" +checksum = "ff159a2da374ef2d64848a6547943cf1af7d2ceada5ae77be175e1389aa07ae3" dependencies = [ "anyhow", "async-trait", "backon", - "base64 0.21.7", + "base64 0.22.0", "bytes", "chrono", + "crc32c", "flagset", "futures", "getrandom", - "http 0.2.9", + "http 1.1.0", "log", "md-5", "once_cell", @@ -7766,7 +8497,7 @@ dependencies = [ "prometheus", "quick-xml 0.31.0", "reqsign", - "reqwest 0.11.20", + "reqwest 0.12.4", "serde", "serde_json", "sha2", @@ -7799,7 +8530,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_plain", - "serde_with", + "serde_with 3.8.0", "sha2", "subtle", "thiserror", @@ -7829,7 +8560,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -7838,15 +8569,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "300.1.6+3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.101" @@ -7855,79 +8577,72 @@ checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] [[package]] name = "opentelemetry" -version = "0.21.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.0.0", "js-sys", "once_cell", "pin-project-lite", "thiserror", - "urlencoding", ] [[package]] name = "opentelemetry-otlp" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f24cda83b20ed2433c68241f918d0f6fdec8b1d43b7a9590ab4420c5095ca930" +checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" dependencies = [ "async-trait", "futures-core", "http 0.2.9", "opentelemetry", "opentelemetry-proto", - "opentelemetry-semantic-conventions", "opentelemetry_sdk", - "prost 0.11.9", + "prost 0.12.1", "thiserror", "tokio", - "tonic 0.9.2", + "tonic 0.11.0", ] [[package]] name = "opentelemetry-proto" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2e155ce5cc812ea3d1dffbd1539aed653de4bf4882d60e6e04dcf0901d674e1" +checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" dependencies = [ "opentelemetry", "opentelemetry_sdk", - "prost 0.11.9", - "tonic 0.9.2", + "prost 0.12.1", + "tonic 0.11.0", ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5774f1ef1f982ef2a447f6ee04ec383981a3ab99c8e77a1a7b30182e65bbc84" -dependencies = [ - "opentelemetry", -] +checksum = "1869fb4bb9b35c5ba8a1e40c9b128a7b4c010d07091e864a29da19e4fe2ca4d7" [[package]] name = "opentelemetry_sdk" -version = "0.21.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f16aec8a98a457a52664d69e0091bac3a0abd18ead9b641cb00202ba4e0efe4" +checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" dependencies = [ "async-trait", - "crossbeam-channel", "futures-channel", "futures-executor", "futures-util", "glob", + "lazy_static", "once_cell", "opentelemetry", "ordered-float 4.1.1", @@ -8026,7 +8741,7 @@ dependencies = [ "proc-macro-error 1.0.4", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8049,9 +8764,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "3.5.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" [[package]] name = "p256" @@ -8171,7 +8886,7 @@ version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bfe55df96e3f02f11bf197ae37d91bb79801631f82f6195dd196ef521df3597" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-cast 48.0.1", @@ -8180,16 +8895,50 @@ dependencies = [ "arrow-schema 48.0.1", "arrow-select 48.0.1", "base64 0.21.7", - "brotli", + "brotli 3.5.0", + "bytes", + "chrono", + "flate2", + "futures", + "hashbrown 0.14.3", + "lz4_flex", + "num", + "num-bigint", + "object_store", + "paste", + "seq-macro", + "snap", + "thrift", + "tokio", + "twox-hash", + "zstd 0.13.0", +] + +[[package]] +name = "parquet" +version = "50.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "547b92ebf0c1177e3892f44c8f79757ee62e678d564a9834189725f2c5b7a750" +dependencies = [ + "ahash 0.8.11", + "arrow-array 50.0.0", + "arrow-buffer 50.0.0", + "arrow-cast 50.0.0", + "arrow-data 50.0.0", + "arrow-ipc 50.0.0", + "arrow-schema 50.0.0", + "arrow-select 50.0.0", + "base64 0.21.7", + "brotli 3.5.0", "bytes", "chrono", "flate2", "futures", + "half 2.3.1", "hashbrown 0.14.3", "lz4_flex", "num", "num-bigint", - "object_store", "paste", "seq-macro", "snap", @@ -8201,20 +8950,20 @@ dependencies = [ [[package]] name = "parquet" -version = "50.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "547b92ebf0c1177e3892f44c8f79757ee62e678d564a9834189725f2c5b7a750" -dependencies = [ - "ahash 0.8.6", - "arrow-array 50.0.0", - "arrow-buffer 50.0.0", - "arrow-cast 50.0.0", - "arrow-data 50.0.0", - "arrow-ipc 50.0.0", - "arrow-schema 50.0.0", - "arrow-select 50.0.0", - "base64 0.21.7", - "brotli", +version = "52.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c3b5322cc1bbf67f11c079c42be41a55949099b78732f7dba9e15edde40eab" +dependencies = [ + "ahash 0.8.11", + "arrow-array 52.0.0", + "arrow-buffer 52.0.0", + "arrow-cast 52.0.0", + "arrow-data 52.0.0", + "arrow-ipc 52.0.0", + "arrow-schema 52.0.0", + "arrow-select 52.0.0", + "base64 0.22.0", + "brotli 6.0.0", "bytes", "chrono", "flate2", @@ -8231,6 +8980,7 @@ dependencies = [ "tokio", "twox-hash", "zstd 0.13.0", + "zstd-sys", ] [[package]] @@ -8255,7 +9005,7 @@ dependencies = [ "regex", "regex-syntax 0.8.2", "structmeta", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8319,6 +9069,15 @@ dependencies = [ "prost-types 0.11.9", ] +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -8361,7 +9120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.0.0", + "indexmap 2.2.6", ] [[package]] @@ -8369,7 +9128,7 @@ name = "pg_bigdecimal" version = "0.1.5" source = "git+https://github.com/risingwavelabs/rust-pg_bigdecimal?rev=0b7893d88894ca082b4525f94f812da034486f7c#0b7893d88894ca082b4525f94f812da034486f7c" dependencies = [ - "bigdecimal 0.4.2", + "bigdecimal 0.4.5", "byteorder", "bytes", "num-bigint", @@ -8390,7 +9149,7 @@ dependencies = [ [[package]] name = "pgwire" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "auto_enums", @@ -8403,7 +9162,7 @@ dependencies = [ "openssl", "panic-message", "parking_lot 0.12.1", - "reqwest 0.12.2", + "reqwest 0.12.4", "risingwave_common", "risingwave_sqlparser", "serde", @@ -8457,7 +9216,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8487,7 +9246,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8522,7 +9281,7 @@ dependencies = [ "aes", "cbc", "der 0.7.8", - "pbkdf2", + "pbkdf2 0.12.2", "scrypt", "sha2", "spki 0.7.2", @@ -8562,6 +9321,16 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" +[[package]] +name = "plotlib" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9462104f987d8d0f6625f0c7764f1c8b890bd1dc8584d8293e031f25c5a0d242" +dependencies = [ + "failure", + "svg", +] + [[package]] name = "plotters" version = "0.3.5" @@ -8598,7 +9367,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8631,9 +9400,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.4.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31114a898e107c51bb1609ffaf55a0e011cf6a4d7f1170d0015a165082c0338b" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] name = "postgres-derive" @@ -8644,7 +9413,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8764,14 +9533,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04bfa62906ce8d9badf8d1764501640ae7f0bcea3437a209315830e0f73564d1" [[package]] -name = "prepare_ci_pubsub" -version = "0.1.0" -dependencies = [ - "anyhow", - "google-cloud-googleapis", - "google-cloud-pubsub", - "madsim-tokio", -] +name = "pretty-hex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5" [[package]] name = "pretty-xmlish" @@ -8806,7 +9571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8828,15 +9593,6 @@ dependencies = [ "toml_edit 0.19.15", ] -[[package]] -name = "proc-macro-crate" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" -dependencies = [ - "toml_edit 0.20.2", -] - [[package]] name = "proc-macro-crate" version = "3.1.0" @@ -8910,7 +9666,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" dependencies = [ "proc-macro-rules-macros", "proc-macro2", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -8922,14 +9678,14 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -9060,7 +9816,7 @@ dependencies = [ "prost 0.12.1", "prost-types 0.12.1", "regex", - "syn 2.0.57", + "syn 2.0.66", "tempfile", "which 4.4.2", ] @@ -9088,7 +9844,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -9097,7 +9853,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -9137,9 +9893,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "protobuf-native" -version = "0.2.1+3.19.1" +version = "0.2.2+3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86df76d0f2a6933036e8a9f28f1adc8b48081fa681dba07eaa30ac75663f7f4e" +checksum = "577feb02952883f2348ee6f36afeeb4e0ee22be0e5b707d60bdc2f5aab038e8f" dependencies = [ "cxx", "cxx-build", @@ -9200,9 +9956,9 @@ dependencies = [ [[package]] name = "pulsar" -version = "6.1.0" +version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d21c6a837986cf25d22ac5b951c267d95808f3c830ff009c2879fff259a0268" +checksum = "d7f3541ff84e39da334979ac4bf171e0f277f4f782603aeae65bf5795dc7275a" dependencies = [ "async-trait", "bit-vec", @@ -9238,9 +9994,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" dependencies = [ "cfg-if", "indoc", @@ -9256,9 +10012,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" dependencies = [ "once_cell", "target-lexicon", @@ -9266,9 +10022,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" dependencies = [ "libc", "pyo3-build-config", @@ -9276,27 +10032,27 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "pyo3-macros-backend" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" dependencies = [ "heck 0.4.1", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -9357,9 +10113,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -9543,7 +10299,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -9593,9 +10349,9 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] name = "regex-syntax" @@ -9620,8 +10376,9 @@ dependencies = [ [[package]] name = "reqsign" -version = "0.14.9" -source = "git+https://github.com/wcy-fdu/reqsign.git?rev=002ee2a#002ee2a41749b08bb5336f344e31f514d8fce718" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70fe66d4cd0b5ed9b1abbfe639bf6baeaaf509f7da2d51b31111ba945be59286" dependencies = [ "anyhow", "async-trait", @@ -9632,14 +10389,14 @@ dependencies = [ "hex", "hmac", "home", - "http 0.2.9", + "http 1.1.0", "jsonwebtoken", "log", "once_cell", "percent-encoding", "quick-xml 0.31.0", "rand", - "reqwest 0.11.20", + "reqwest 0.12.4", "rsa", "rust-ini", "serde", @@ -9673,8 +10430,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.10", - "rustls-native-certs 0.6.3", + "rustls 0.21.11", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -9690,24 +10446,24 @@ dependencies = [ "wasm-streams 0.3.0", "web-sys", "webpki-roots 0.25.2", - "winreg", + "winreg 0.50.0", ] [[package]] name = "reqwest" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ "async-compression", - "base64 0.21.7", + "base64 0.22.0", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", "h2 0.4.4", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "hyper 1.1.0", @@ -9718,12 +10474,13 @@ dependencies = [ "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.22.3", - "rustls-pemfile 1.0.4", + "rustls 0.22.4", + "rustls-pemfile 2.1.1", "rustls-pki-types", "serde", "serde_json", @@ -9742,7 +10499,22 @@ dependencies = [ "wasm-streams 0.4.0", "web-sys", "webpki-roots 0.26.1", - "winreg", + "winreg 0.52.0", +] + +[[package]] +name = "reqwest-middleware" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45d100244a467870f6cb763c4484d010a6bed6bd610b3676e3825d93fb4cfbd" +dependencies = [ + "anyhow", + "async-trait", + "http 1.1.0", + "reqwest 0.12.4", + "serde", + "thiserror", + "tower-service", ] [[package]] @@ -9822,7 +10594,7 @@ dependencies = [ [[package]] name = "risedev" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "chrono", @@ -9837,10 +10609,10 @@ dependencies = [ "madsim-tokio", "redis", "regex", - "reqwest 0.12.2", + "reqwest 0.12.4", "serde", "serde_json", - "serde_with", + "serde_with 3.8.0", "serde_yaml", "tempfile", "thiserror-ext", @@ -9852,7 +10624,7 @@ dependencies = [ [[package]] name = "risedev-config" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "clap", @@ -9865,19 +10637,19 @@ dependencies = [ [[package]] name = "risingwave-fields-derive" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "expect-test", "indoc", "prettyplease 0.2.15", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "risingwave_backup" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "async-trait", @@ -9899,14 +10671,16 @@ dependencies = [ [[package]] name = "risingwave_batch" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "arrow-array 50.0.0", + "arrow-array 52.0.0", "arrow-schema 50.0.0", "assert_matches", "async-recursion", "async-trait", + "bytes", "criterion", "either", "foyer", @@ -9915,14 +10689,16 @@ dependencies = [ "futures-util", "hashbrown 0.14.3", "hytra", - "icelake", + "iceberg", "itertools 0.12.1", "madsim-tokio", "madsim-tonic", "memcomparable", + "opendal", "parking_lot 0.12.1", "paste", "prometheus", + "prost 0.12.1", "rand", "risingwave_common", "risingwave_common_estimate_size", @@ -9945,12 +10721,14 @@ dependencies = [ "tokio-stream", "tokio-util", "tracing", + "twox-hash", + "uuid", "workspace-hack", ] [[package]] name = "risingwave_bench" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "async-trait", @@ -9968,9 +10746,10 @@ dependencies = [ "itertools 0.12.1", "libc", "madsim-tokio", - "nix 0.28.0", + "nix 0.29.0", "opentelemetry", "parking_lot 0.12.1", + "plotlib", "prometheus", "rand", "risingwave_common", @@ -9991,7 +10770,7 @@ dependencies = [ [[package]] name = "risingwave_cmd" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "clap", "madsim-tokio", @@ -10011,7 +10790,7 @@ dependencies = [ [[package]] name = "risingwave_cmd_all" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "clap", @@ -10026,14 +10805,13 @@ dependencies = [ "risingwave_compactor", "risingwave_compute", "risingwave_ctl", - "risingwave_expr", "risingwave_expr_impl", "risingwave_frontend", "risingwave_meta_node", "risingwave_rt", "shell-words", - "strum 0.26.1", - "strum_macros 0.26.1", + "strum 0.26.2", + "strum_macros 0.26.4", "tempfile", "thiserror-ext", "tikv-jemallocator", @@ -10045,18 +10823,23 @@ dependencies = [ [[package]] name = "risingwave_common" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ + "ahash 0.8.11", "anyhow", "arc-swap", "arrow-array 48.0.1", "arrow-array 50.0.0", + "arrow-array 52.0.0", "arrow-buffer 48.0.1", "arrow-buffer 50.0.0", + "arrow-buffer 52.0.0", "arrow-cast 48.0.1", "arrow-cast 50.0.0", + "arrow-cast 52.0.0", "arrow-schema 48.0.1", "arrow-schema 50.0.0", + "arrow-schema 52.0.0", "async-trait", "auto_enums", "auto_impl", @@ -10064,14 +10847,15 @@ dependencies = [ "byteorder", "bytes", "chrono", - "chrono-tz", + "chrono-tz 0.9.0", "clap", + "coarsetime", "comfy-table", "crc32fast", "criterion", "darwin-libproc", "easy-ext", - "educe 0.5.7", + "educe", "either", "enum-as-inner 0.6.0", "enumflags2", @@ -10081,6 +10865,7 @@ dependencies = [ "foyer", "futures", "governor", + "hashbrown 0.14.3", "hex", "http 0.2.9", "http-body 0.4.5", @@ -10111,12 +10896,13 @@ dependencies = [ "prost 0.12.1", "rand", "regex", - "reqwest 0.12.2", + "reqwest 0.12.4", "risingwave-fields-derive", "risingwave_common_estimate_size", "risingwave_common_metrics", "risingwave_common_proc_macro", "risingwave_error", + "risingwave_license", "risingwave_pb", "rust_decimal", "rusty-fork", @@ -10127,19 +10913,20 @@ dependencies = [ "serde_bytes", "serde_default", "serde_json", - "serde_with", + "serde_with 3.8.0", "smallbitset", "speedate", "stacker", "static_assertions", - "strum 0.26.1", - "strum_macros 0.26.1", + "strum 0.26.2", + "strum_macros 0.26.4", "sysinfo", "tempfile", "thiserror", "thiserror-ext", "tinyvec", "tokio-retry", + "tokio-util", "toml 0.8.12", "tower-layer", "tower-service", @@ -10155,10 +10942,10 @@ dependencies = [ [[package]] name = "risingwave_common_estimate_size" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "bytes", - "educe 0.5.7", + "educe", "ethnum", "fixedbitset 0.5.0", "jsonbb", @@ -10170,7 +10957,7 @@ dependencies = [ [[package]] name = "risingwave_common_heap_profiling" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "chrono", @@ -10185,7 +10972,7 @@ dependencies = [ [[package]] name = "risingwave_common_metrics" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "bytes", "clap", @@ -10217,7 +11004,7 @@ dependencies = [ [[package]] name = "risingwave_common_proc_macro" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "bae", "itertools 0.12.1", @@ -10229,7 +11016,7 @@ dependencies = [ [[package]] name = "risingwave_common_service" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "async-trait", "axum 0.7.4", @@ -10251,7 +11038,7 @@ dependencies = [ [[package]] name = "risingwave_compaction_test" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "async-trait", @@ -10279,7 +11066,7 @@ dependencies = [ [[package]] name = "risingwave_compactor" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "async-trait", "await-tree", @@ -10302,7 +11089,7 @@ dependencies = [ [[package]] name = "risingwave_compute" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "async-trait", @@ -10347,21 +11134,27 @@ dependencies = [ [[package]] name = "risingwave_connector" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "apache-avro 0.16.0", "arrow-array 50.0.0", + "arrow-array 52.0.0", "arrow-row 50.0.0", "arrow-schema 50.0.0", + "arrow-schema 52.0.0", "arrow-select 50.0.0", "assert_matches", + "async-compression", "async-nats", "async-trait", "auto_enums", "auto_impl", + "await-tree", "aws-config", "aws-credential-types", + "aws-msk-iam-sasl-signer", + "aws-sdk-dynamodb", "aws-sdk-kinesis", "aws-sdk-s3", "aws-smithy-http", @@ -10372,6 +11165,7 @@ dependencies = [ "base64 0.22.0", "byteorder", "bytes", + "cfg-or-panic", "chrono", "clickhouse", "criterion", @@ -10385,19 +11179,25 @@ dependencies = [ "futures-async-stream", "gcp-bigquery-client", "glob", + "google-cloud-bigquery", + "google-cloud-gax", + "google-cloud-googleapis", "google-cloud-pubsub", "http 0.2.9", + "iceberg", + "iceberg-catalog-rest", "icelake", - "indexmap 1.9.3", + "indexmap 2.2.6", "itertools 0.12.1", "jni", - "jsonschema-transpiler", + "jsonbb", "jsonwebtoken", "madsim-rdkafka", "madsim-tokio", "madsim-tonic", "maplit", "moka", + "mongodb", "mysql_async", "mysql_common", "nexmark", @@ -10422,9 +11222,10 @@ dependencies = [ "rand", "redis", "regex", - "reqwest 0.12.2", + "reqwest 0.12.4", "risingwave_common", "risingwave_common_estimate_size", + "risingwave_connector_codec", "risingwave_jni_core", "risingwave_object_store", "risingwave_pb", @@ -10435,18 +11236,21 @@ dependencies = [ "rustls-pemfile 2.1.1", "rustls-pki-types", "rw_futures_util", + "sea-schema", "serde", "serde_derive", "serde_json", - "serde_with", + "serde_with 3.8.0", "serde_yaml", "simd-json", - "strum 0.26.1", - "strum_macros 0.26.1", + "sqlx", + "strum 0.26.2", + "strum_macros 0.26.4", "syn 1.0.109", "tempfile", "thiserror", "thiserror-ext", + "tiberius", "time", "tokio-postgres", "tokio-retry", @@ -10455,6 +11259,7 @@ dependencies = [ "tracing", "tracing-subscriber", "tracing-test", + "typed-builder 0.18.2", "url", "urlencoding", "uuid", @@ -10464,21 +11269,48 @@ dependencies = [ "yup-oauth2", ] +[[package]] +name = "risingwave_connector_codec" +version = "1.11.0-alpha" +dependencies = [ + "anyhow", + "apache-avro 0.16.0", + "chrono", + "easy-ext", + "expect-test", + "hex", + "itertools 0.12.1", + "jsonbb", + "jsonschema-transpiler", + "num-bigint", + "risingwave_common", + "risingwave_pb", + "rust_decimal", + "serde_json", + "thiserror", + "thiserror-ext", + "time", + "tracing", + "workspace-hack", +] + [[package]] name = "risingwave_ctl" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "bytes", "chrono", "clap", "comfy-table", + "foyer", "futures", "hex", "inquire", "itertools 0.12.1", "madsim-etcd-client", "madsim-tokio", + "madsim-tonic", "memcomparable", "prost 0.12.1", "regex", @@ -10507,7 +11339,7 @@ dependencies = [ [[package]] name = "risingwave_dml" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "assert_matches", "criterion", @@ -10531,7 +11363,7 @@ dependencies = [ [[package]] name = "risingwave_e2e_extended_mode_test" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "chrono", @@ -10546,7 +11378,7 @@ dependencies = [ [[package]] name = "risingwave_error" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "bincode 1.3.3", @@ -10562,15 +11394,11 @@ dependencies = [ [[package]] name = "risingwave_expr" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "arrow-array 50.0.0", "arrow-schema 50.0.0", - "arrow-udf-js", - "arrow-udf-js-deno", - "arrow-udf-python", - "arrow-udf-wasm", "async-trait", "auto_impl", "await-tree", @@ -10579,7 +11407,7 @@ dependencies = [ "const-currying", "downcast-rs", "easy-ext", - "educe 0.5.7", + "educe", "either", "enum-as-inner 0.6.0", "expect-test", @@ -10589,42 +11417,48 @@ dependencies = [ "itertools 0.12.1", "linkme", "madsim-tokio", - "md5", - "moka", "num-traits", - "openssl", "parse-display", "paste", + "prometheus", "risingwave_common", "risingwave_common_estimate_size", "risingwave_expr_macro", "risingwave_pb", - "risingwave_udf", "smallvec", "static_assertions", "thiserror", "thiserror-ext", "tracing", "workspace-hack", - "zstd 0.13.0", ] [[package]] name = "risingwave_expr_impl" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "aho-corasick", "anyhow", + "arrow-array 50.0.0", + "arrow-flight", "arrow-schema 50.0.0", + "arrow-schema 52.0.0", + "arrow-udf-flight", + "arrow-udf-js", + "arrow-udf-js-deno", + "arrow-udf-python", + "arrow-udf-wasm", "async-trait", "auto_enums", "chrono", - "chrono-tz", + "chrono-tz 0.9.0", "criterion", + "educe", "expect-test", "fancy-regex", "futures-async-stream", "futures-util", + "ginepro", "hex", "icelake", "itertools 0.12.1", @@ -10632,6 +11466,7 @@ dependencies = [ "linkme", "madsim-tokio", "md5", + "moka", "num-traits", "openssl", "regex", @@ -10639,17 +11474,21 @@ dependencies = [ "risingwave_common_estimate_size", "risingwave_expr", "risingwave_pb", + "risingwave_sqlparser", "rust_decimal", "self_cell", "serde", "serde_json", "sha1", "sha2", + "smallvec", "sql-json-path", "thiserror", "thiserror-ext", + "tonic 0.10.2", "tracing", "workspace-hack", + "zstd 0.13.0", ] [[package]] @@ -10659,17 +11498,17 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "risingwave_frontend" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "arc-swap", "arrow-schema 50.0.0", - "arrow-udf-wasm", + "arrow-schema 52.0.0", "assert_matches", "async-recursion", "async-trait", @@ -10682,7 +11521,7 @@ dependencies = [ "downcast-rs", "dyn-clone", "easy-ext", - "educe 0.5.7", + "educe", "either", "enum-as-inner 0.6.0", "expect-test", @@ -10691,6 +11530,7 @@ dependencies = [ "futures", "futures-async-stream", "iana-time-zone", + "iceberg", "icelake", "itertools 0.12.1", "jsonbb", @@ -10728,7 +11568,6 @@ dependencies = [ "risingwave_rpc_client", "risingwave_sqlparser", "risingwave_storage", - "risingwave_udf", "risingwave_variables", "rw_futures_util", "serde", @@ -10748,16 +11587,16 @@ dependencies = [ [[package]] name = "risingwave_frontend_macro" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "risingwave_hummock_sdk" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "bytes", "easy-ext", @@ -10768,13 +11607,15 @@ dependencies = [ "risingwave_common", "risingwave_common_estimate_size", "risingwave_pb", + "serde", + "serde_bytes", "tracing", "workspace-hack", ] [[package]] name = "risingwave_hummock_test" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "async-trait", "bytes", @@ -10807,7 +11648,7 @@ dependencies = [ [[package]] name = "risingwave_hummock_trace" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "async-trait", "bincode 2.0.0-rc.3", @@ -10832,14 +11673,25 @@ dependencies = [ name = "risingwave_java_binding" version = "0.1.0" dependencies = [ + "anyhow", + "bytes", + "cfg-or-panic", + "foyer", + "futures", "jni", + "madsim-tokio", "prost 0.12.1", "risingwave_common", "risingwave_expr", + "risingwave_hummock_sdk", "risingwave_jni_core", + "risingwave_object_store", "risingwave_pb", + "risingwave_storage", + "rw_futures_util", "serde", "serde_json", + "tracing", ] [[package]] @@ -10851,6 +11703,7 @@ dependencies = [ "cfg-or-panic", "chrono", "expect-test", + "foyer", "fs-err", "futures", "itertools 0.12.1", @@ -10861,9 +11714,7 @@ dependencies = [ "risingwave_common", "risingwave_expr", "risingwave_hummock_sdk", - "risingwave_object_store", "risingwave_pb", - "risingwave_storage", "rw_futures_util", "serde", "serde_json", @@ -10872,9 +11723,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "risingwave_license" +version = "1.11.0-alpha" +dependencies = [ + "expect-test", + "jsonwebtoken", + "serde", + "thiserror", + "thiserror-ext", + "tracing", +] + [[package]] name = "risingwave_mem_table_spill_test" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "async-trait", "bytes", @@ -10890,8 +11753,9 @@ dependencies = [ [[package]] name = "risingwave_meta" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ + "aes-siv", "anyhow", "arc-swap", "assert_matches", @@ -10899,6 +11763,7 @@ dependencies = [ "aws-config", "axum 0.7.4", "base64-url", + "bincode 1.3.3", "bytes", "chrono", "clap", @@ -10909,6 +11774,7 @@ dependencies = [ "enum-as-inner 0.6.0", "expect-test", "fail", + "flate2", "function_name", "futures", "hex", @@ -10947,7 +11813,7 @@ dependencies = [ "sea-orm", "serde", "serde_json", - "strum 0.26.1", + "strum 0.26.2", "sync-point", "thiserror", "thiserror-ext", @@ -10963,7 +11829,7 @@ dependencies = [ [[package]] name = "risingwave_meta_dashboard" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "axum 0.7.4", @@ -10973,7 +11839,7 @@ dependencies = [ "dircpy", "mime_guess", "npm_rs", - "reqwest 0.12.2", + "reqwest 0.12.4", "rust-embed", "thiserror-ext", "tokio", @@ -10984,16 +11850,18 @@ dependencies = [ [[package]] name = "risingwave_meta_model_migration" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "async-std", "sea-orm-migration", + "serde", + "serde_json", "uuid", ] [[package]] name = "risingwave_meta_model_v2" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "prost 0.12.1", "risingwave_common", @@ -11006,7 +11874,7 @@ dependencies = [ [[package]] name = "risingwave_meta_node" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "clap", @@ -11038,7 +11906,7 @@ dependencies = [ [[package]] name = "risingwave_meta_service" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "async-trait", @@ -11066,7 +11934,7 @@ dependencies = [ [[package]] name = "risingwave_object_store" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "async-trait", "await-tree", @@ -11090,8 +11958,9 @@ dependencies = [ "madsim-tokio", "opendal", "prometheus", - "reqwest 0.11.20", + "reqwest 0.12.4", "risingwave_common", + "risingwave_jni_core", "rustls 0.23.5", "spin 0.9.8", "thiserror", @@ -11102,7 +11971,7 @@ dependencies = [ [[package]] name = "risingwave_pb" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "enum-as-inner 0.6.0", "fs-err", @@ -11114,7 +11983,7 @@ dependencies = [ "prost-helpers", "risingwave_error", "serde", - "strum 0.26.1", + "strum 0.26.2", "thiserror", "walkdir", "workspace-hack", @@ -11122,19 +11991,19 @@ dependencies = [ [[package]] name = "risingwave_planner_test" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "expect-test", "itertools 0.12.1", - "libtest-mimic 0.7.0", + "libtest-mimic", "madsim-tokio", "paste", "risingwave_expr_impl", "risingwave_frontend", "risingwave_sqlparser", "serde", - "serde_with", + "serde_with 3.8.0", "serde_yaml", "tempfile", "thiserror-ext", @@ -11144,7 +12013,7 @@ dependencies = [ [[package]] name = "risingwave_regress_test" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "clap", @@ -11158,7 +12027,7 @@ dependencies = [ [[package]] name = "risingwave_rpc_client" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "async-trait", @@ -11192,7 +12061,7 @@ dependencies = [ [[package]] name = "risingwave_rt" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "await-tree", "console", @@ -11201,6 +12070,8 @@ dependencies = [ "futures", "hostname 0.4.0", "madsim-tokio", + "minitrace", + "minitrace-opentelemetry", "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", @@ -11273,42 +12144,35 @@ dependencies = [ [[package]] name = "risingwave_sqlparser" -version = "1.9.0-alpha" -dependencies = [ - "itertools 0.12.1", - "matches", - "serde", - "tracing", - "tracing-subscriber", - "workspace-hack", -] - -[[package]] -name = "risingwave_sqlparser_test_runner" -version = "0.1.0" +version = "1.11.0-alpha" dependencies = [ "anyhow", "console", - "futures", + "itertools 0.12.1", + "libtest-mimic", "madsim-tokio", - "risingwave_sqlparser", + "matches", "serde", - "serde_with", + "serde_with 3.8.0", "serde_yaml", + "thiserror", + "tracing", + "tracing-subscriber", "walkdir", + "winnow 0.6.11", "workspace-hack", ] [[package]] name = "risingwave_sqlsmith" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "chrono", "clap", "expect-test", "itertools 0.12.1", - "libtest-mimic 0.7.0", + "libtest-mimic", "madsim-tokio", "rand", "rand_chacha", @@ -11329,7 +12193,7 @@ dependencies = [ [[package]] name = "risingwave_state_cleaning_test" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "clap", @@ -11339,7 +12203,7 @@ dependencies = [ "regex", "risingwave_rt", "serde", - "serde_with", + "serde_with 3.8.0", "tokio-postgres", "tokio-stream", "toml 0.8.12", @@ -11349,14 +12213,15 @@ dependencies = [ [[package]] name = "risingwave_storage" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "anyhow", "arc-swap", "async-trait", "auto_enums", "await-tree", + "bincode 1.3.3", "bytes", "criterion", "crossbeam", @@ -11379,9 +12244,10 @@ dependencies = [ "madsim-tokio", "madsim-tonic", "memcomparable", + "metrics-prometheus", "moka", "more-asserts", - "nix 0.28.0", + "nix 0.29.0", "num-integer", "parking_lot 0.12.1", "procfs 0.16.0", @@ -11400,6 +12266,8 @@ dependencies = [ "risingwave_rpc_client", "risingwave_test_runner", "scopeguard", + "serde", + "serde_bytes", "sled", "spin 0.9.8", "sync-point", @@ -11417,7 +12285,7 @@ dependencies = [ [[package]] name = "risingwave_stream" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "anyhow", "assert_matches", @@ -11430,7 +12298,7 @@ dependencies = [ "cfg-if", "criterion", "delta_btree_map", - "educe 0.5.7", + "educe", "either", "enum-as-inner 0.6.0", "expect-test", @@ -11471,8 +12339,8 @@ dependencies = [ "serde_yaml", "smallvec", "static_assertions", - "strum 0.26.1", - "strum_macros 0.26.1", + "strum 0.26.2", + "strum_macros 0.26.4", "thiserror", "thiserror-ext", "tokio-metrics", @@ -11485,38 +12353,16 @@ dependencies = [ [[package]] name = "risingwave_test_runner" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "fail", "sync-point", "workspace-hack", ] -[[package]] -name = "risingwave_udf" -version = "0.1.0" -dependencies = [ - "arrow-array 50.0.0", - "arrow-flight", - "arrow-schema 50.0.0", - "arrow-select 50.0.0", - "cfg-or-panic", - "futures", - "futures-util", - "ginepro", - "madsim-tokio", - "madsim-tonic", - "prometheus", - "risingwave_common", - "static_assertions", - "thiserror", - "thiserror-ext", - "tracing", -] - [[package]] name = "risingwave_variables" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "chrono", "workspace-hack", @@ -11578,27 +12424,27 @@ dependencies = [ [[package]] name = "rquickjs" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7f63201fa6f2ff8173e4758ea552549d687d8f63003361a8b5c50f7c446ded" +checksum = "9cbd33e0b668aea0ab238b9164523aca929096f9f40834700d71d91dd4888882" dependencies = [ "rquickjs-core", ] [[package]] name = "rquickjs-core" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad00eeddc0f88af54ee202c8385fb214fe0423897c056a7df8369fb482e3695" +checksum = "2e9129d69b7b8f7ee8ad1da5b12c7f4a8a8acd45f2e6dd9cb2ee1bc5a1f2fa3d" dependencies = [ "rquickjs-sys", ] [[package]] name = "rquickjs-sys" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120dbbc3296de9b96de8890091635d46f3506cd38b4e8f21800c386c035d64fa" +checksum = "bf6f2288d8e7fbb5130f62cf720451641e99d55f6fde9db86aa2914ecb553fd2" dependencies = [ "cc", ] @@ -11624,6 +12470,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rtrb" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e704dd104faf2326a320140f70f0b736d607c1caa1b1748a6c568a79819109" +dependencies = [ + "cache-padded", +] + [[package]] name = "rumqttc" version = "0.24.0" @@ -11645,9 +12500,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.3.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" +checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -11656,23 +12511,23 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.3.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" +checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", "shellexpand 3.1.0", - "syn 2.0.57", + "syn 2.0.66", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.3.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" +checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" dependencies = [ "mime_guess", "sha2", @@ -11681,12 +12536,13 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" +checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41" dependencies = [ "cfg-if", "ordered-multimap", + "trim-in-place", ] [[package]] @@ -11736,6 +12592,16 @@ dependencies = [ "semver 1.0.18", ] +[[package]] +name = "rustc_version_runtime" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f" +dependencies = [ + "rustc_version 0.2.3", + "semver 0.9.0", +] + [[package]] name = "rustix" version = "0.36.16" @@ -11781,9 +12647,21 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", "ring 0.17.5", @@ -11793,9 +12671,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring 0.17.5", @@ -11814,6 +12692,7 @@ dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring 0.17.5", "rustls-pki-types", "rustls-webpki 0.102.2", "subtle", @@ -11866,9 +12745,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-tokio-stream" @@ -11877,7 +12756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "696a389edb0b54b9bb888c8318404d9a0c0b9091fb03ca3d9c64731511db03f6" dependencies = [ "futures", - "rustls 0.22.3", + "rustls 0.22.4", "socket2 0.5.6", "tokio", ] @@ -11975,9 +12854,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "ryu-js" @@ -12005,9 +12884,9 @@ dependencies = [ [[package]] name = "sasl2-sys" -version = "0.1.20+2.1.28" +version = "0.1.22+2.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e645bd98535fc8fd251c43ba7c7c1f9be1e0369c99b6a5ea719052a773e655c" +checksum = "05f2a7f7efd9fc98b3a9033272df10709f5ee3fa0eabbd61a527a3a1ed6bd3c6" dependencies = [ "cc", "duct", @@ -12022,6 +12901,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" +[[package]] +name = "scc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.22" @@ -12037,7 +12925,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "cfg-if", "hashbrown 0.13.2", ] @@ -12066,7 +12954,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "pbkdf2", + "pbkdf2 0.12.2", "salsa20", "sha2", ] @@ -12081,6 +12969,12 @@ dependencies = [ "untrusted 0.7.1", ] +[[package]] +name = "sdd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" + [[package]] name = "sea-bae" version = "0.2.0" @@ -12091,7 +12985,7 @@ dependencies = [ "proc-macro-error 1.0.4", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -12149,7 +13043,7 @@ dependencies = [ "proc-macro2", "quote", "sea-bae", - "syn 2.0.57", + "syn 2.0.66", "unicode-ident", ] @@ -12225,7 +13119,9 @@ checksum = "30d148608012d25222442d1ebbfafd1228dbc5221baf4ec35596494e27a2394e" dependencies = [ "futures", "sea-query", + "sea-query-binder", "sea-schema-derive", + "sqlx", ] [[package]] @@ -12258,6 +13154,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "sealed" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "sec1" version = "0.3.0" @@ -12347,9 +13255,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -12396,13 +13304,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -12418,9 +13326,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -12463,7 +13371,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -12502,32 +13410,54 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.7.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" dependencies = [ - "base64 0.21.7", + "serde", + "serde_with_macros 1.5.2", +] + +[[package]] +name = "serde_with" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c85f8e96d1d6857f13768fcbd895fcb06225510022a2774ed8b5150581847b0" +dependencies = [ + "base64 0.22.0", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.0.0", + "indexmap 2.2.6", "serde", "serde_derive", "serde_json", - "serde_with_macros", + "serde_with_macros 3.8.0", "time", ] [[package]] name = "serde_with_macros" -version = "3.7.0" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +checksum = "c8b3a576c4eb2924262d5951a3b737ccaf16c931e39a2810c36f9a7e25575557" dependencies = [ - "darling 0.20.8", + "darling 0.20.9", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -12536,7 +13466,7 @@ version = "0.9.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -12545,27 +13475,27 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d" +checksum = "adb86f9315df5df6a70eae0cc22395a44e544a0d8897586820770a35ede74449" dependencies = [ - "dashmap", "futures", - "lazy_static", "log", + "once_cell", "parking_lot 0.12.1", + "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" +checksum = "a9bb72430492e9549b0c4596725c0f82729bff861c45aa8099c0a8e67fc3b721" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13012,18 +13942,18 @@ dependencies = [ [[package]] name = "sqllogictest" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7c6a33098cd55e4fead1bd1f85c1d2064f02bafdb9fe004ca39fd94aee36e6" +checksum = "1106ff933150aa8b8af71ee57631cd3ac2feeccd230d122ad833e7f0cd5e8fe1" dependencies = [ "async-trait", - "educe 0.4.23", + "educe", "fs-err", "futures", "glob", "humantime", - "itertools 0.11.0", - "libtest-mimic 0.6.1", + "itertools 0.13.0", + "libtest-mimic", "md-5", "owo-colors", "regex", @@ -13074,7 +14004,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "atoi", "bigdecimal 0.3.1", "byteorder", @@ -13084,7 +14014,7 @@ dependencies = [ "crossbeam-queue", "dotenvy", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -13092,7 +14022,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.0.0", + "indexmap 2.2.6", "log", "memchr", "native-tls", @@ -13310,7 +14240,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13332,9 +14262,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "structmeta" @@ -13345,7 +14275,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13356,7 +14286,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13370,11 +14300,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ - "strum_macros 0.26.1", + "strum_macros 0.26.4", ] [[package]] @@ -13387,20 +14317,20 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "strum_macros" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13429,6 +14359,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "svg" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3685c82a045a6af0c488f0550b0f52b4c77d2a52b0ca8aba719f9d268fa96965" + [[package]] name = "swc_atoms" version = "0.6.5" @@ -13447,7 +14383,7 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630c761c74ac8021490b78578cc2223aa4a568241e26505c27bf0e4fd4ad8ec2" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "anyhow", "dashmap", "once_cell", @@ -13488,7 +14424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce837c5eae1cb200a310940de989fd9b3d12ed62d7752bc69b39ef8aa775ec04" dependencies = [ "anyhow", - "indexmap 2.0.0", + "indexmap 2.2.6", "serde", "serde_json", "swc_cached", @@ -13504,7 +14440,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13553,7 +14489,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13600,7 +14536,7 @@ checksum = "66539401f619730b26d380a120b91b499f80cbdd9bb15d00aa73bc3a4d4cc394" dependencies = [ "better_scoped_tls", "bitflags 2.5.0", - "indexmap 2.0.0", + "indexmap 2.2.6", "once_cell", "phf", "rustc-hash", @@ -13638,7 +14574,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13669,7 +14605,7 @@ checksum = "f0ec75c1194365abe4d44d94e58f918ec853469ecd39733b381a089cfdcdee1a" dependencies = [ "base64 0.21.7", "dashmap", - "indexmap 2.0.0", + "indexmap 2.2.6", "once_cell", "serde", "sha-1", @@ -13708,7 +14644,7 @@ version = "0.127.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14482e455df85486d68a51533a31645d511e56df93a35cadf0eabbe7abe96b98" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "num_cpus", "once_cell", "rustc-hash", @@ -13742,7 +14678,7 @@ checksum = "695a1d8b461033d32429b5befbf0ad4d7a2c4d6ba9cd5ba4e0645c615839e8e4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13753,7 +14689,7 @@ checksum = "50176cfc1cbc8bb22f41c6fe9d1ec53fbe057001219b5954961b8ad0f336fce9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13777,7 +14713,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13816,9 +14752,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.57" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -13845,7 +14781,7 @@ dependencies = [ "proc-macro-error 1.0.4", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -13864,6 +14800,18 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + [[package]] name = "sysinfo" version = "0.30.0" @@ -13921,6 +14869,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + [[package]] name = "tap" version = "1.0.1" @@ -13971,9 +14925,9 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] @@ -13997,18 +14951,18 @@ dependencies = [ "either", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -14052,6 +15006,37 @@ dependencies = [ "ordered-float 2.10.0", ] +[[package]] +name = "tiberius" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6e2bf3e4b5be181a2a2ceff4b9b12e2684010d436a6958bd564fbc8094d44d" +dependencies = [ + "async-trait", + "asynchronous-codec", + "bigdecimal 0.3.1", + "byteorder", + "bytes", + "chrono", + "connection-string", + "encoding", + "enumflags2", + "futures-util", + "num-traits", + "once_cell", + "pin-project-lite", + "pretty-hex", + "rust_decimal", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.4", + "thiserror", + "time", + "tokio-rustls 0.23.4", + "tokio-util", + "tracing", + "uuid", +] + [[package]] name = "tikv-jemalloc-ctl" version = "0.5.4" @@ -14183,7 +15168,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -14255,13 +15240,24 @@ dependencies = [ "rand", ] +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.9", + "tokio", + "webpki", +] + [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.10", + "rustls 0.21.11", "tokio", ] @@ -14271,7 +15267,18 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.22.3", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.5", "rustls-pki-types", "tokio", ] @@ -14306,6 +15313,7 @@ checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -14351,31 +15359,20 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", "winnow 0.5.15", ] -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.0.0", - "toml_datetime", - "winnow 0.5.15", -] - [[package]] name = "toml_edit" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.15", ] @@ -14386,25 +15383,25 @@ version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.5", + "winnow 0.6.11", ] [[package]] name = "tonic" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ + "async-stream", "async-trait", "axum 0.6.20", "base64 0.21.7", "bytes", - "futures-core", - "futures-util", + "flate2", "h2 0.3.26", "http 0.2.9", "http-body 0.4.5", @@ -14412,27 +15409,30 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.11.9", + "prost 0.12.1", + "rustls 0.21.11", + "rustls-pemfile 1.0.4", "tokio", + "tokio-rustls 0.24.1", "tokio-stream", "tower", "tower-layer", "tower-service", "tracing", + "webpki-roots 0.25.2", ] [[package]] name = "tonic" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" dependencies = [ "async-stream", "async-trait", "axum 0.6.20", "base64 0.21.7", "bytes", - "flate2", "h2 0.3.26", "http 0.2.9", "http-body 0.4.5", @@ -14441,16 +15441,12 @@ dependencies = [ "percent-encoding", "pin-project", "prost 0.12.1", - "rustls 0.21.10", - "rustls-pemfile 1.0.4", "tokio", - "tokio-rustls 0.24.1", "tokio-stream", "tower", "tower-layer", "tower-service", "tracing", - "webpki-roots 0.25.2", ] [[package]] @@ -14463,7 +15459,7 @@ dependencies = [ "proc-macro2", "prost-build 0.12.1", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -14497,7 +15493,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.0.0", + "http 1.1.0", "http-body 1.0.0", "http-body-util", "http-range-header", @@ -14545,7 +15541,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -14594,9 +15590,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596" +checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" dependencies = [ "js-sys", "once_cell", @@ -14666,6 +15662,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "trim-in-place" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" + [[package]] name = "triomphe" version = "0.1.11" @@ -14682,6 +15684,31 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622b09ce2fe2df4618636fb92176d205662f59803f39e70d1c333393082de96c" +[[package]] +name = "trust-dns-proto" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner 0.4.0", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "log", + "rand", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "url", +] + [[package]] name = "trust-dns-proto" version = "0.22.0" @@ -14733,6 +15760,26 @@ dependencies = [ "url", ] +[[package]] +name = "trust-dns-resolver" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "parking_lot 0.12.1", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto 0.21.2", +] + [[package]] name = "trust-dns-resolver" version = "0.22.0" @@ -14809,6 +15856,17 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "typed-builder" version = "0.16.2" @@ -14820,11 +15878,11 @@ dependencies = [ [[package]] name = "typed-builder" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47c0496149861b7c95198088cbf36645016b1a0734cf350c50e2a38e070f38a" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" dependencies = [ - "typed-builder-macro 0.18.0", + "typed-builder-macro 0.18.2", ] [[package]] @@ -14835,18 +15893,18 @@ checksum = "f03ca4cb38206e2bef0700092660bb74d696f808514dae47fa1467cbfe26e96e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "typed-builder-macro" -version = "0.18.0" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982ee4197351b5c9782847ef5ec1fdcaf50503fb19d68f9771adae314e72b492" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -15082,9 +16140,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.4.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92ccd67fb88503048c01b59152a04effd0782d035a83a6d256ce6085f08f4a3" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" [[package]] name = "value-trait" @@ -15176,9 +16234,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi-common" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b53dfacdeacca15ee2a48a4aa0ec6a6d0da737676e465770c0585f79c04e638" +checksum = "63255d85e10627b07325d7cf4e5fe5a40fa4ff183569a0a67931be26d50ede07" dependencies = [ "anyhow", "bitflags 2.5.0", @@ -15227,7 +16285,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -15261,7 +16319,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -15274,9 +16332,18 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" -version = "0.201.0" +version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" +checksum = "bfd106365a7f5f7aa3c1916a98cbb3ad477f5ff96ddb130285a91c6e7429e67a" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.206.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d759312e1137f199096d80a70be685899cd7d3d09c572836bb2e9b69b4dc3b1e" dependencies = [ "leb128", ] @@ -15309,20 +16376,20 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.201.0" +version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" +checksum = "d6998515d3cf3f8b980ef7c11b29a9b1017d4cf86b99ae93b546992df9931413" dependencies = [ "bitflags 2.5.0", - "indexmap 2.0.0", + "indexmap 2.2.6", "semver 1.0.18", ] [[package]] name = "wasmprinter" -version = "0.201.0" +version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a67e66da702706ba08729a78e3c0079085f6bfcb1a62e4799e97bbf728c2c265" +checksum = "ab1cc9508685eef9502e787f4d4123745f5651a1e29aec047645d3cac1e2da7a" dependencies = [ "anyhow", "wasmparser", @@ -15330,9 +16397,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516be5b58a8f75d39b01378516dcb0ff7b9bc39c7f1f10eec5b338d4916cf988" +checksum = "5a5990663c28d81015ddbb02a068ac1bf396a4ea296eba7125b2dfc7c00cb52e" dependencies = [ "addr2line", "anyhow", @@ -15343,11 +16410,11 @@ dependencies = [ "encoding_rs", "fxprof-processed-profile", "gimli", - "indexmap 2.0.0", + "indexmap 2.2.6", "ittapi", "libc", "log", - "object", + "object 0.33.0", "once_cell", "paste", "rayon", @@ -15357,7 +16424,7 @@ dependencies = [ "serde_derive", "serde_json", "target-lexicon", - "wasm-encoder", + "wasm-encoder 0.202.0", "wasmparser", "wasmtime-cache", "wasmtime-component-macro", @@ -15376,18 +16443,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d22d88a92d69385f18143c946884bf6aaa9ec206ce54c85a2d320c1362b009" +checksum = "625ee94c72004f3ea0228989c9506596e469517d7d0ed66f7300d1067bdf1ca9" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068728a840223b56c964507550da671372e7e5c2f3a7856012b57482e3e979a7" +checksum = "98534bf28de232299e83eab33984a7a6c40c69534d6bd0ea216150b63d41a83a" dependencies = [ "anyhow", "base64 0.21.7", @@ -15405,14 +16472,14 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631244bac89c57ebe7283209d86fe175ad5929328e75f61bf9141895cafbf52d" +checksum = "64f84414a25ee3a624c8b77550f3fe7b5d8145bd3405ca58886ee6900abb6dc2" dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -15420,15 +16487,15 @@ dependencies = [ [[package]] name = "wasmtime-component-util" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ad496ba0558f7602da5e9d4c201f35f7aefcca70f973ec916f3f0d0787ef74" +checksum = "78580bdb4e04c7da3bf98088559ca1d29382668536e4d5c7f2f966d79c390307" [[package]] name = "wasmtime-cranelift" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961ab5ee4b17e627001b18069ee89ef906edbbd3f84955515f6aad5ab6d82299" +checksum = "b60df0ee08c6a536c765f69e9e8205273435b66d02dd401e938769a2622a6c1a" dependencies = [ "anyhow", "cfg-if", @@ -15440,51 +16507,34 @@ dependencies = [ "cranelift-wasm", "gimli", "log", - "object", + "object 0.33.0", "target-lexicon", "thiserror", "wasmparser", - "wasmtime-cranelift-shared", "wasmtime-environ", "wasmtime-versioned-export-macros", ] -[[package]] -name = "wasmtime-cranelift-shared" -version = "19.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4db94596be14cd1f85844ce85470bf68acf235143098b9d9bf72b49e47b917" -dependencies = [ - "anyhow", - "cranelift-codegen", - "cranelift-control", - "cranelift-native", - "gimli", - "object", - "target-lexicon", - "wasmtime-environ", -] - [[package]] name = "wasmtime-environ" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420b13858ef27dfd116f1fdb0513e9593a307a632ade2ea58334b639a3d8d24e" +checksum = "64ffc1613db69ee47c96738861534f9a405e422a5aa00224fbf5d410b03fb445" dependencies = [ "anyhow", "bincode 1.3.3", "cpp_demangle", "cranelift-entity", "gimli", - "indexmap 2.0.0", + "indexmap 2.2.6", "log", - "object", + "object 0.33.0", "rustc-demangle", "serde", "serde_derive", "target-lexicon", "thiserror", - "wasm-encoder", + "wasm-encoder 0.202.0", "wasmparser", "wasmprinter", "wasmtime-component-util", @@ -15493,9 +16543,9 @@ dependencies = [ [[package]] name = "wasmtime-fiber" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d37ff0e11a023019e34fe839c74a1c00880b989f4446176b6cc6da3b58e3ef2" +checksum = "f043514a23792761c5765f8ba61a4aa7d67f260c0c37494caabceb41d8ae81de" dependencies = [ "anyhow", "cc", @@ -15508,11 +16558,11 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b849f19ad1d4a8133ff05b82c438144f17fb49b08e5f7995f8c1e25cf35f390" +checksum = "9c0ca2ad8f5d2b37f507ef1c935687a690e84e9f325f5a2af9639440b43c1f0e" dependencies = [ - "object", + "object 0.33.0", "once_cell", "rustix 0.38.31", "wasmtime-versioned-export-macros", @@ -15520,9 +16570,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c48eb4223d6556ffbf3decb146d0da124f1fd043f41c98b705252cb6a5c186" +checksum = "7a9f93a3289057b26dc75eb84d6e60d7694f7d169c7c09597495de6e016a13ff" dependencies = [ "cfg-if", "libc", @@ -15531,45 +16581,45 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fefac2cb5f5a6f365234a3584bf40bd2e45e7f6cd90a689d9b2afbb9881978f" +checksum = "c6332a2b0af4224c3ea57c857ad39acd2780ccc2b0c99ba1baa01864d90d7c94" dependencies = [ "anyhow", "cc", "cfg-if", "encoding_rs", - "indexmap 2.0.0", + "indexmap 2.2.6", "libc", "log", - "mach", + "mach2", "memfd", "memoffset", "paste", "psm", "rustix 0.38.31", "sptr", - "wasm-encoder", + "wasm-encoder 0.202.0", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-fiber", "wasmtime-jit-debug", + "wasmtime-slab", "wasmtime-versioned-export-macros", - "wasmtime-wmemcheck", "windows-sys 0.52.0", ] [[package]] name = "wasmtime-slab" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d7b97b92df126fdbe994a53d2215828ec5ed5087535e6d4703b1fbd299f0e3" +checksum = "8b3655075824a374c536a2b2cc9283bb765fcdf3d58b58587862c48571ad81ef" [[package]] name = "wasmtime-types" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509c88abb830819b259c49e2d4e4f22b555db066ba08ded0b76b071a2aa53ddf" +checksum = "b98cf64a242b0b9257604181ca28b28a5fcaa4c9ea1d396f76d1d2d1c5b40eef" dependencies = [ "cranelift-entity", "serde", @@ -15580,50 +16630,44 @@ dependencies = [ [[package]] name = "wasmtime-versioned-export-macros" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d81c092a61ca1667013e2eb08fed7c6c53e496dbbaa32d5685dc5152b0a772" +checksum = "8561d9e2920db2a175213d557d71c2ac7695831ab472bbfafb9060cd1034684f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] name = "wasmtime-winch" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0958907880e37a2d3974f5b3574c23bf70aaf1fc6c1f716625bb50dac776f1a" +checksum = "a06b573d14ac846a0fb8c541d8fca6a64acf9a1d176176982472274ab1d2fa5d" dependencies = [ "anyhow", "cranelift-codegen", "gimli", - "object", + "object 0.33.0", "target-lexicon", "wasmparser", - "wasmtime-cranelift-shared", + "wasmtime-cranelift", "wasmtime-environ", "winch-codegen", ] [[package]] name = "wasmtime-wit-bindgen" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a593ddefd2f80617df6bea084b2e422d8969e924bc209642a794d57518f59587" +checksum = "595bc7bb3b0ff4aa00fab718c323ea552c3034d77abc821a35112552f2ea487a" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.0.0", + "indexmap 2.2.6", "wit-parser", ] -[[package]] -name = "wasmtime-wmemcheck" -version = "19.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77212b6874bbc86d220bb1d28632d0c11c6afe996c3e1ddcf746b1a6b4919b9" - [[package]] name = "wast" version = "35.0.2" @@ -15635,24 +16679,24 @@ dependencies = [ [[package]] name = "wast" -version = "201.0.0" +version = "206.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa" +checksum = "68586953ee4960b1f5d84ebf26df3b628b17e6173bc088e0acfbce431469795a" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.206.0", ] [[package]] name = "wat" -version = "1.201.0" +version = "1.206.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453d5b37a45b98dee4f4cb68015fc73634d7883bbef1c65e6e9c78d454cf3f32" +checksum = "da4c6f2606276c6e991aebf441b2fc92c517807393f039992a3e0ad873efe4ad" dependencies = [ - "wast 201.0.0", + "wast 206.0.0", ] [[package]] @@ -15673,14 +16717,24 @@ dependencies = [ [[package]] name = "web-time" -version = "0.2.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57099a701fb3a8043f993e8228dc24229c7b942e2b009a1b962e54489ba1d3bf" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.5", + "untrusted 0.9.0", +] + [[package]] name = "webpki-roots" version = "0.25.2" @@ -15740,9 +16794,9 @@ checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "wiggle" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f093d8afdb09efaf2ed1037468bd4614308a762d215b6cafd60a7712993a8ffa" +checksum = "1b6552dda951239e219c329e5a768393664e8d120c5e0818487ac2633f173b1f" dependencies = [ "anyhow", "async-trait", @@ -15755,28 +16809,28 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c7bccd5172ce8d853242f723e42c84b8c131b24fb07a1570f9045d99258616" +checksum = "da64cb31e0bfe8b1d2d13956ef9fd5c77545756a1a6ef0e6cfd44e8f1f207aed" dependencies = [ "anyhow", "heck 0.4.1", "proc-macro2", "quote", "shellexpand 2.1.2", - "syn 2.0.57", + "syn 2.0.66", "witx", ] [[package]] name = "wiggle-macro" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69d087dee85991096fc0c6eaf4dcf4e17cd16a0594c33b8ab9e2d345234ef75" +checksum = "900b2416ef2ff2903ded6cf55d4a941fed601bf56a8c4874856d7a77c1891994" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", "wiggle-generate", ] @@ -15813,9 +16867,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72a6a7034793b874b85e428fd6d7b3ccccb98c326e33af3aa40cdf50d0c33da" +checksum = "fb23450977f9d4a23c02439cf6899340b2d68887b19465c5682740d9cc37d52e" dependencies = [ "anyhow", "cranelift-codegen", @@ -15824,6 +16878,7 @@ dependencies = [ "smallvec", "target-lexicon", "wasmparser", + "wasmtime-cranelift", "wasmtime-environ", ] @@ -16083,9 +17138,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.5" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" +checksum = "56c52728401e1dc672a56e81e593e912aa54c78f40246869f78359a2bf24d29d" dependencies = [ "memchr", ] @@ -16100,6 +17155,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winx" version = "0.36.3" @@ -16112,13 +17177,13 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.201.0" +version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" +checksum = "744237b488352f4f27bca05a10acb79474415951c450e52ebd0da784c1df2bcc" dependencies = [ "anyhow", "id-arena", - "indexmap 2.0.0", + "indexmap 2.2.6", "log", "semver 1.0.18", "serde", @@ -16130,11 +17195,11 @@ dependencies = [ [[package]] name = "with_options" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -16151,12 +17216,11 @@ dependencies = [ [[package]] name = "workspace-config" -version = "1.9.0-alpha" +version = "1.11.0-alpha" dependencies = [ "libz-sys", "log", "lzma-sys", - "openssl-sys", "sasl2-sys", "tracing", "zstd-sys", @@ -16164,7 +17228,7 @@ dependencies = [ [[package]] name = "workspace-hack" -version = "1.9.0-alpha" +version = "1.11.0-alpha" [[package]] name = "wyz" @@ -16249,7 +17313,7 @@ dependencies = [ "itertools 0.10.5", "log", "percent-encoding", - "rustls 0.21.10", + "rustls 0.21.11", "rustls-pemfile 1.0.4", "seahash", "serde", @@ -16283,7 +17347,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] @@ -16303,7 +17367,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.57", + "syn 2.0.66", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ce1a66c94bdaa..80f3f5af2e8c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "scripts/source/prepare_ci_pubsub", "src/batch", "src/bench", "src/cmd", @@ -13,6 +12,7 @@ members = [ "src/common/metrics", "src/compute", "src/connector", + "src/connector/codec", "src/connector/with_options", "src/ctl", "src/dml", @@ -20,12 +20,12 @@ members = [ "src/expr/core", "src/expr/impl", "src/expr/macro", - "src/expr/udf", "src/frontend", "src/frontend/macro", "src/frontend/planner_test", "src/java_binding", "src/jni_core", + "src/license", "src/meta", "src/meta/dashboard", "src/meta/model_v2", @@ -39,7 +39,6 @@ members = [ "src/risedevtool/config", "src/rpc_client", "src/sqlparser", - "src/sqlparser/test_runner", "src/storage", "src/storage/backup", "src/storage/compactor", @@ -70,7 +69,7 @@ exclude = ["e2e_test/udf/wasm", "lints"] resolver = "2" [workspace.package] -version = "1.9.0-alpha" +version = "1.11.0-alpha" edition = "2021" homepage = "https://github.com/risingwavelabs/risingwave" keywords = ["sql", "database", "streaming"] @@ -78,7 +77,13 @@ license = "Apache-2.0" repository = "https://github.com/risingwavelabs/risingwave" [workspace.dependencies] -foyer = "0.6" +foyer = { version = "0.10.0", features = ["nightly", "mtrace"] } +apache-avro = { git = "https://github.com/risingwavelabs/avro", rev = "25113ba88234a9ae23296e981d8302c290fdaa4b", features = [ + "snappy", + "zstandard", + "bzip", + "xz", +] } auto_enums = { version = "0.8", features = ["futures03", "tokio1"] } await-tree = "0.2.1" aws-config = { version = "1", default-features = false, features = [ @@ -118,7 +123,7 @@ axum = "=0.7.4" # TODO: 0.7.5+ does not work with current toolchain etcd-client = { package = "madsim-etcd-client", version = "0.4" } futures-async-stream = "0.2.9" hytra = "0.1" -rdkafka = { package = "madsim-rdkafka", version = "0.3.4", features = [ +rdkafka = { package = "madsim-rdkafka", version = "0.4.1", features = [ "cmake-build", ] } hashbrown = { version = "0.14", features = ["ahash", "inline-more", "nightly"] } @@ -127,9 +132,17 @@ tonic = { package = "madsim-tonic", version = "0.4.1" } tonic-build = { package = "madsim-tonic-build", version = "0.4.2" } otlp-embedded = { git = "https://github.com/risingwavelabs/otlp-embedded", rev = "492c244e0be91feb659c0cd48a624bbd96045a33" } prost = { version = "0.12" } -icelake = { git = "https://github.com/icelake-io/icelake", rev = "54fd72fbd1dd8c592f05eeeb79223c8a6a33c297", features = [ +icelake = { git = "https://github.com/icelake-io/icelake", rev = "07d53893d7788b4e41fc11efad8a6be828405c31", features = [ "prometheus", ] } +arrow-array-iceberg = { package = "arrow-array", version = "52" } +arrow-schema-iceberg = { package = "arrow-schema", version = "52" } +arrow-buffer-iceberg = { package = "arrow-buffer", version = "52" } +arrow-cast-iceberg = { package = "arrow-cast", version = "52" } +# TODO +# After apache/iceberg-rust#411 is merged, we move to the upstream version. +iceberg = { git = "https://github.com/risingwavelabs/iceberg-rust.git", rev = "0c6e133e6f4655ff9ce4ad57b577dc7f692dd902" } +iceberg-catalog-rest = { git = "https://github.com/risingwavelabs/iceberg-rust.git", rev = "0c6e133e6f4655ff9ce4ad57b577dc7f692dd902" } arrow-array = "50" arrow-arith = "50" arrow-cast = "50" @@ -139,10 +152,11 @@ arrow-flight = "50" arrow-select = "50" arrow-ord = "50" arrow-row = "50" -arrow-udf-js = "0.1" -arrow-udf-js-deno = { git = "https://github.com/risingwavelabs/arrow-udf.git", rev = "23fe0dd" } -arrow-udf-wasm = { version = "0.2.1", features = ["build"] } -arrow-udf-python = { git = "https://github.com/risingwavelabs/arrow-udf.git", rev = "6c32f71" } +arrow-udf-js = "0.3.1" +arrow-udf-js-deno = { git = "https://github.com/risingwavelabs/arrow-udf.git", rev = "fa36365" } +arrow-udf-wasm = { version = "0.2.2", features = ["build"] } +arrow-udf-python = "0.2" +arrow-udf-flight = "0.1" arrow-array-deltalake = { package = "arrow-array", version = "48.0.1" } arrow-buffer-deltalake = { package = "arrow-buffer", version = "48.0.1" } arrow-cast-deltalake = { package = "arrow-cast", version = "48.0.1" } @@ -155,6 +169,7 @@ deltalake = { git = "https://github.com/risingwavelabs/delta-rs", rev = "5c2dccd "gcs", ] } itertools = "0.12.0" +jsonbb = "0.1.4" lru = { git = "https://github.com/risingwavelabs/lru-rs.git", rev = "2682b85" } parquet = "50" thiserror-ext = "0.1.2" @@ -163,10 +178,10 @@ tikv-jemallocator = { git = "https://github.com/risingwavelabs/jemallocator.git" "profiling", "stats", ], rev = "64a2d9" } -opentelemetry = "0.21" -opentelemetry-otlp = "0.14" -opentelemetry_sdk = { version = "0.21", default-features = false } -opentelemetry-semantic-conventions = "0.13" +opentelemetry = "0.23" +opentelemetry-otlp = "0.16" +opentelemetry_sdk = { version = "0.23", default-features = false } +opentelemetry-semantic-conventions = "0.15" parking_lot = { version = "0.12", features = [ "arc_lock", "deadlock_detection", @@ -177,8 +192,9 @@ sea-orm = { version = "0.12.14", features = [ "sqlx-sqlite", "runtime-tokio-native-tls", ] } +sqlx = "0.7" tokio-util = "0.7" -tracing-opentelemetry = "0.22" +tracing-opentelemetry = "0.24" rand = { version = "0.8", features = ["small_rng"] } risingwave_backup = { path = "./src/storage/backup" } risingwave_batch = { path = "./src/batch" } @@ -193,6 +209,7 @@ risingwave_compactor = { path = "./src/storage/compactor" } risingwave_compute = { path = "./src/compute" } risingwave_ctl = { path = "./src/ctl" } risingwave_connector = { path = "./src/connector" } +risingwave_connector_codec = { path = "./src/connector/codec" } risingwave_dml = { path = "./src/dml" } risingwave_error = { path = "./src/error" } risingwave_expr = { path = "./src/expr/core" } @@ -201,6 +218,7 @@ risingwave_frontend = { path = "./src/frontend" } risingwave_hummock_sdk = { path = "./src/storage/hummock_sdk" } risingwave_hummock_test = { path = "./src/storage/hummock_test" } risingwave_hummock_trace = { path = "./src/storage/hummock_trace" } +risingwave_license = { path = "./src/license" } risingwave_mem_table_spill_test = { path = "./src/stream/spill_test" } risingwave_meta = { path = "./src/meta" } risingwave_meta_dashboard = { path = "./src/meta/dashboard" } @@ -251,8 +269,11 @@ await_holding_lock = "warn" ptr_arg = "allow" # a little pedantic get_first = "allow" -# https://github.com/rust-lang/rust-clippy/issues/12016 -blocks_in_conditions = "allow" +new_without_default = "allow" +# TODO: remove later https://github.com/rust-lang/rust-clippy/issues/12537 +# duplicated_attributes = "allow" +# TODO: remove later https://github.com/rust-lang/rust-clippy/issues/12436 +mixed_attributes_style = "allow" [workspace.lints.rustdoc] private_intra_doc_links = "allow" @@ -314,8 +335,6 @@ tokio-postgres = { git = "https://github.com/madsim-rs/rust-postgres.git", rev = futures-timer = { git = "https://github.com/madsim-rs/futures-timer.git", rev = "05b33b4" } # patch: unlimit 4MB message size for grpc client etcd-client = { git = "https://github.com/risingwavelabs/etcd-client.git", rev = "4e84d40" } -# todo(wcy-fdu): remove this patch fork after opendal release a new version to apply azure workload identity change. -reqsign = { git = "https://github.com/wcy-fdu/reqsign.git", rev = "002ee2a" } # patch to remove preserve_order from serde_json deno_core = { git = "https://github.com/bakjos/deno_core", rev = "9b241c6" } # patch to user reqwest 0.12.2 @@ -325,6 +344,8 @@ deno_net = { git = "https://github.com/bakjos/deno", rev = "787a232" } deno_tls = { git = "https://github.com/bakjos/deno", rev = "787a232" } deno_web = { git = "https://github.com/bakjos/deno", rev = "787a232" } deno_websocket = { git = "https://github.com/bakjos/deno", rev = "787a232" } +# patch to remove preserve_order from serde_json +bson = { git = "https://github.com/risingwavelabs/bson-rust", rev = "e5175ec" } [workspace.metadata.dylint] libraries = [{ path = "./lints" }] diff --git a/Makefile.toml b/Makefile.toml index 504ff88a33d5a..a93e832f1859f 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -1,103 +1,121 @@ extend = [ - { path = "src/risedevtool/grafana.toml" }, - { path = "src/risedevtool/prometheus.toml" }, - { path = "src/risedevtool/minio.toml" }, - { path = "src/risedevtool/etcd.toml" }, - { path = "src/risedevtool/tempo.toml" }, - { path = "src/risedevtool/kafka.toml" }, - { path = "src/risedevtool/gcloud-pubsub.toml" }, - { path = "src/risedevtool/redis.toml" }, - { path = "src/risedevtool/connector.toml" }, - { path = "src/risedevtool/risedev-components.toml" }, - { path = "src/sqlparser/test_runner/sqlparser_test.toml" }, - { path = "src/frontend/planner_test/planner_test.toml" }, - { path = "src/tests/compaction_test/Makefile.toml" }, - { path = "src/storage/backup/integration_tests/Makefile.toml" }, - { path = "src/java_binding/make-java-binding.toml" }, - { path = "src/stream/tests/integration_tests/integration_test.toml" }, - { path = "e2e_test/source_inline/commands.toml" }, + { path = "src/risedevtool/grafana.toml" }, + { path = "src/risedevtool/prometheus.toml" }, + { path = "src/risedevtool/minio.toml" }, + { path = "src/risedevtool/etcd.toml" }, + { path = "src/risedevtool/tempo.toml" }, + { path = "src/risedevtool/gcloud-pubsub.toml" }, + { path = "src/risedevtool/redis.toml" }, + { path = "src/risedevtool/connector.toml" }, + { path = "src/risedevtool/risedev-components.toml" }, + { path = "src/sqlparser/sqlparser_test.toml" }, + { path = "src/frontend/planner_test/planner_test.toml" }, + { path = "src/tests/compaction_test/Makefile.toml" }, + { path = "src/storage/backup/integration_tests/Makefile.toml" }, + { path = "src/java_binding/make-java-binding.toml" }, + { path = "src/stream/tests/integration_tests/integration_test.toml" }, + { path = "e2e_test/source_inline/commands.toml" }, ] env_files = ["./risedev-components.user.env"] env_scripts = [ - ''' -#!@duckscript - -# only duckscript can modify env variables in cargo-make -# duckscript doc: https://github.com/sagiegurari/duckscript/blob/master/docs/sdk.md + ''' + #!@duckscript + + # only duckscript can modify env variables in cargo-make + # duckscript doc: https://github.com/sagiegurari/duckscript/blob/master/docs/sdk.md + + set_env ENABLE_TELEMETRY "false" + set_env RW_TELEMETRY_TYPE "test" + + is_sanitizer_enabled = get_env ENABLE_SANITIZER + is_hdfs_backend = get_env ENABLE_HDFS + is_release = get_env ENABLE_RELEASE_PROFILE + is_not_release = not ${is_release} + is_dynamic_linking = get_env ENABLE_DYNAMIC_LINKING + is_hummock_trace = get_env ENABLE_HUMMOCK_TRACE + is_external_udf_enabled = get_env ENABLE_EXTERNAL_UDF + is_wasm_udf_enabled = get_env ENABLE_WASM_UDF + is_js_udf_enabled = get_env ENABLE_JS_UDF + is_deno_udf_enabled = get_env ENABLE_DENO_UDF + is_python_udf_enabled = get_env ENABLE_PYTHON_UDF + + if ${is_sanitizer_enabled} + set_env RISEDEV_CARGO_BUILD_EXTRA_ARGS "--timings -Zbuild-std --target ${CARGO_MAKE_RUST_TARGET_TRIPLE}" + set_env RISEDEV_BUILD_TARGET_DIR "${CARGO_MAKE_RUST_TARGET_TRIPLE}/" + set_env RISEDEV_RUSTFLAGS "-Ctarget-cpu=native --cfg tokio_unstable -Zsanitizer=thread" + else + set_env RISEDEV_CARGO_BUILD_EXTRA_ARGS "--timings" + set_env RISEDEV_BUILD_TARGET_DIR "" + end -set_env ENABLE_TELEMETRY "false" + if ${is_hdfs_backend} + set_env BUILD_HDFS_BACKEND_CMD "-p risingwave_object_store --features hdfs-backend" + else + set_env BUILD_HDFS_BACKEND_CMD "" + end -is_sanitizer_enabled = get_env ENABLE_SANITIZER -is_hdfs_backend = get_env ENABLE_HDFS -is_release = get_env ENABLE_RELEASE_PROFILE -is_not_release = not ${is_release} -is_dynamic_linking = get_env ENABLE_DYNAMIC_LINKING -is_hummock_trace = get_env ENABLE_HUMMOCK_TRACE -is_deno_udf_enabled = get_env ENABLE_DENO_UDF -is_python_udf_enabled = get_env ENABLE_PYTHON_UDF + if ${is_not_release} and ${is_dynamic_linking} + set_env RISINGWAVE_FEATURE_FLAGS "--features rw-dynamic-link --no-default-features" + else + set_env RISINGWAVE_FEATURE_FLAGS "--features rw-static-link" + end -if ${is_sanitizer_enabled} - set_env RISEDEV_CARGO_BUILD_EXTRA_ARGS "-Zbuild-std --target ${CARGO_MAKE_RUST_TARGET_TRIPLE}" - set_env RISEDEV_BUILD_TARGET_DIR "${CARGO_MAKE_RUST_TARGET_TRIPLE}/" - set_env RISEDEV_RUSTFLAGS "-Ctarget-cpu=native --cfg tokio_unstable -Zsanitizer=thread" -else - set_env RISEDEV_CARGO_BUILD_EXTRA_ARGS "" - set_env RISEDEV_BUILD_TARGET_DIR "" -end + if ${is_external_udf_enabled} + flags = get_env RISINGWAVE_FEATURE_FLAGS + set_env RISINGWAVE_FEATURE_FLAGS "${flags} --features external-udf" + end -if ${is_hdfs_backend} - set_env BUILD_HDFS_BACKEND_CMD "-p risingwave_object_store --features hdfs-backend" -else - set_env BUILD_HDFS_BACKEND_CMD "" -end + if ${is_wasm_udf_enabled} + flags = get_env RISINGWAVE_FEATURE_FLAGS + set_env RISINGWAVE_FEATURE_FLAGS "${flags} --features wasm-udf" + end -if ${is_not_release} and ${is_dynamic_linking} - set_env RISINGWAVE_FEATURE_FLAGS "--features rw-dynamic-link --no-default-features" -else - set_env RISINGWAVE_FEATURE_FLAGS "--features rw-static-link" -end + if ${is_js_udf_enabled} + flags = get_env RISINGWAVE_FEATURE_FLAGS + set_env RISINGWAVE_FEATURE_FLAGS "${flags} --features js-udf" + end -if ${is_deno_udf_enabled} - flags = get_env RISINGWAVE_FEATURE_FLAGS - set_env RISINGWAVE_FEATURE_FLAGS "${flags} --features embedded-deno-udf" -end + if ${is_deno_udf_enabled} + flags = get_env RISINGWAVE_FEATURE_FLAGS + set_env RISINGWAVE_FEATURE_FLAGS "${flags} --features deno-udf" + end -if ${is_python_udf_enabled} - flags = get_env RISINGWAVE_FEATURE_FLAGS - set_env RISINGWAVE_FEATURE_FLAGS "${flags} --features embedded-python-udf" -end + if ${is_python_udf_enabled} + flags = get_env RISINGWAVE_FEATURE_FLAGS + set_env RISINGWAVE_FEATURE_FLAGS "${flags} --features python-udf" + end -if ${is_hummock_trace} - set_env BUILD_HUMMOCK_TRACE_CMD "-p risingwave_storage --features hm-trace" -else - set_env BUILD_HUMMOCK_TRACE_CMD "" -end + if ${is_hummock_trace} + set_env BUILD_HUMMOCK_TRACE_CMD "-p risingwave_storage --features hm-trace" + else + set_env BUILD_HUMMOCK_TRACE_CMD "" + end -is_ci = get_env RISINGWAVE_CI -is_not_ci = not ${is_ci} + is_ci = get_env RISINGWAVE_CI + is_not_ci = not ${is_ci} -if ${is_not_ci} - query_log_path = get_env RW_QUERY_LOG_PATH - no_query_log_path = not ${query_log_path} + if ${is_not_ci} + query_log_path = get_env RW_QUERY_LOG_PATH + no_query_log_path = not ${query_log_path} - if ${no_query_log_path} - set_env RW_QUERY_LOG_PATH "${PREFIX_LOG}" - fi + if ${no_query_log_path} + set_env RW_QUERY_LOG_PATH "${PREFIX_LOG}" + fi - rust_log = get_env RUST_LOG - no_rust_log = not ${rust_log} + rust_log = get_env RUST_LOG + no_rust_log = not ${rust_log} - if ${no_rust_log} - set_env RUST_LOG "pgwire_query_log=info" - else - set_env RUST_LOG "pgwire_query_log=info,${rust_log}" + if ${no_rust_log} + set_env RUST_LOG "pgwire_query_log=info" + else + set_env RUST_LOG "pgwire_query_log=info,${rust_log}" + end end -end -set_env TMUX "tmux -L risedev" -''', + set_env TMUX "tmux -L risedev" + ''', ] @@ -131,8 +149,7 @@ rm -rf "${PREFIX_PROFILING}" [tasks.reset-rw] category = "RiseDev - Start/Stop" description = "Clean all data in the default database dev of the running RisingWave" -dependencies = ["check-risedev-env-file"] -env_files = ["${PREFIX_CONFIG}/risedev-env"] +dependencies = ["check-and-load-risedev-env-file"] script = ''' #!/usr/bin/env bash psql -h $RISEDEV_RW_FRONTEND_LISTEN_ADDRESS -p $RISEDEV_RW_FRONTEND_PORT -U root -d dev -c "CREATE DATABASE risedev_tmp;" @@ -306,7 +323,7 @@ description = "Codesign playground binary to support coredump" # codesign the binary before running. # https://developer.apple.com/forums/thread/694233?answerId=695943022#695943022 condition = { env_set = [ - "ENABLE_COREDUMP", + "ENABLE_COREDUMP", ], env = { "SYSTEM" = "darwin-arm64" } } script = ''' #!/usr/bin/env bash @@ -323,7 +340,7 @@ description = "Codesign all binaries to support coredump" # codesign the binary before running. # https://developer.apple.com/forums/thread/694233?answerId=695943022#695943022 condition = { env_set = [ - "ENABLE_COREDUMP", + "ENABLE_COREDUMP", ], env = { "SYSTEM" = "darwin-arm64" } } script = ''' #!/usr/bin/env bash @@ -358,9 +375,9 @@ category = "RiseDev - Build" description = "Copy RisngWave binaries to bin" condition = { env_set = ["ENABLE_BUILD_RUST"] } dependencies = [ - "link-all-in-one-binaries", - "link-user-bin", - "codesign-binaries", + "link-all-in-one-binaries", + "link-user-bin", + "codesign-binaries", ] [tasks.b] @@ -477,16 +494,15 @@ private = true category = "Misc" description = "Download all available components at once" dependencies = [ - "download-maven", - "download-etcd", - "download-grafana", - "download-tempo", - "download-kafka", - "download-mcli", - "download-minio", - "download-prometheus", - "download-pubsub", - "download-redis", + "download-maven", + "download-etcd", + "download-grafana", + "download-tempo", + "download-mcli", + "download-minio", + "download-prometheus", + "download-pubsub", + "download-redis", ] [tasks.create-user-profiles-file] @@ -494,7 +510,7 @@ private = true category = "RiseDev - Prepare" description = "Create user profiles file if not exists" condition = { files_not_exist = [ - "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/risedev-profiles.user.yml", + "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/risedev-profiles.user.yml", ] } script = ''' #!/usr/bin/env bash @@ -521,35 +537,34 @@ EOF category = "RiseDev - Prepare" description = "Prepare dev cluster by downloading necessary tools and build required components" dependencies = [ - "create-user-profiles-file", - "download-all", - "build-risingwave", - "build-connector-node", - "post-build-risingwave", - "prepare-config", + "create-user-profiles-file", + "download-all", + "build-risingwave", + "build-connector-node", + "post-build-risingwave", + "prepare-config", ] [tasks.pre-start-benchmark] category = "RiseDev - Prepare" description = "Download necessary tools to deploy a benchmark env" dependencies = [ - "download-minio", - "download-mcli", - "download-etcd", - "download-grafana", - "download-prometheus", - "download-tempo", - "download-kafka", - "download-redis", + "download-minio", + "download-mcli", + "download-etcd", + "download-grafana", + "download-prometheus", + "download-tempo", + "download-redis", ] [tasks.pre-start-playground] category = "RiseDev - Prepare" description = "Preparation steps for playground" dependencies = [ - "build-risingwave-playground", - "codesign-playground", - "build-connector-node", + "build-risingwave-playground", + "codesign-playground", + "build-connector-node", ] [tasks.check-risedev-env-file] @@ -568,11 +583,16 @@ if [ ! -f "${RC_ENV_FILE}" ]; then fi ''' +[tasks.check-and-load-risedev-env-file] +private = true +category = "RiseDev - Prepare" +dependencies = ["check-risedev-env-file"] +env_files = ["${PREFIX_CONFIG}/risedev-env"] + [tasks.psql-env] category = "RiseDev - Start/Stop" description = "Dump env configuration for psql" -dependencies = ["check-risedev-env-file"] -env_files = ["${PREFIX_CONFIG}/risedev-env"] +dependencies = ["check-and-load-risedev-env-file"] script = ''' #!/usr/bin/env bash cat < "${PREFIX_CONFIG}/psql-env" @@ -590,8 +610,7 @@ echo " $(tput setaf 4)source ${PREFIX_CONFIG}/psql-env$(tput sgr0)" [tasks.psql] category = "RiseDev - Start/Stop" description = "Run local psql client with default connection parameters. You can pass extra arguments to psql." -dependencies = ["check-risedev-env-file"] -env_files = ["${PREFIX_CONFIG}/risedev-env"] +dependencies = ["check-and-load-risedev-env-file"] script = ''' #!/usr/bin/env bash psql -h $RISEDEV_RW_FRONTEND_LISTEN_ADDRESS -p $RISEDEV_RW_FRONTEND_PORT -U root -d dev "$@" @@ -600,8 +619,7 @@ psql -h $RISEDEV_RW_FRONTEND_LISTEN_ADDRESS -p $RISEDEV_RW_FRONTEND_PORT -U root [tasks.ctl] category = "RiseDev - Start/Stop" description = "Start RiseCtl" -dependencies = ["check-risedev-env-file"] -env_files = ["${PREFIX_CONFIG}/risedev-env"] +dependencies = ["check-and-load-risedev-env-file"] script = ''' #!/usr/bin/env bash cargo run -p risingwave_cmd_all --profile "${RISINGWAVE_BUILD_PROFILE}" -- ctl "$@" @@ -687,54 +705,21 @@ script = ''' set -euo pipefail -wait_kafka_exit() { - # Follow kafka-server-stop.sh - while [[ -n "$(ps ax | grep ' kafka\.Kafka ' | grep java | grep -v grep | awk '{print $1}')" ]]; do - echo "Waiting for kafka to exit" - sleep 1 - done -} - -wait_zookeeper_exit() { - # Follow zookeeper-server-stop.sh - while [[ -n "$(ps ax | grep java | grep -i QuorumPeerMain | grep -v grep | awk '{print $1}')" ]]; do - echo "Waiting for zookeeper to exit" - sleep 1 - done -} - -kill_kafka() { - ${PREFIX_BIN}/kafka/bin/kafka-server-stop.sh - wait_kafka_exit -} - -kill_zookeeper() { - ${PREFIX_BIN}/kafka/bin/zookeeper-server-stop.sh - wait_zookeeper_exit -} - if ! ${TMUX} ls &>/dev/null ; then echo "No risedev cluster to kill. Exiting..." exit 0 fi -# Kill other components +# Kill other components with Ctrl+C/Ctrl+D ${TMUX} list-windows -F "#{window_name} #{pane_id}" \ -| grep -v 'kafka' \ -| grep -v 'zookeeper' \ | awk '{ print $2 }' \ | xargs -I {} ${TMUX} send-keys -t {} C-c C-d -if [[ -n $(${TMUX} list-windows | grep kafka) ]]; -then - echo "kill kafka" - kill_kafka || true - - echo "kill zookeeper" - kill_zookeeper || true - - # Kill their ${TMUX} sessions - ${TMUX} list-windows -t risedev -F "#{pane_id}" | xargs -I {} ${TMUX} send-keys -t {} C-c C-d +# Stop docker components +containers=$(docker ps -a -q -f name=risedev- 2>/dev/null) || true +if [[ -n ${containers} ]]; then + echo "Stopping docker components..." + docker stop ${containers} fi ${TMUX} kill-server @@ -756,16 +741,16 @@ dependencies = ["k", "clean-data"] private = true category = "RiseDev - Check" install_crate = { min_version = "0.9.51", crate_name = "cargo-nextest", binary = "cargo", test_arg = [ - "nextest", - "--help", + "nextest", + "--help", ], install_command = "binstall" } [tasks.install-llvm-cov] private = true category = "RiseDev - Check" install_crate = { min_version = "0.5.17", crate_name = "cargo-llvm-cov", binary = "cargo", test_arg = [ - "llvm-cov", - "--help", + "llvm-cov", + "--help", ], install_command = "binstall" } [tasks.install-tools] @@ -846,11 +831,11 @@ description = "🌟 Run unit tests" category = "RiseDev - Build" dependencies = ["prepare"] condition = { env_set = [ - "ENABLE_BUILD_RW_CONNECTOR", + "ENABLE_BUILD_RW_CONNECTOR", ], files_modified = { input = [ - "./java/connector-node/**/*", + "./java/connector-node/**/*", ], output = [ - "./java/connector-node/assembly/target/**/*", + "./java/connector-node/assembly/target/**/*", ] } } description = "Build RisingWave Connector from source" script = ''' @@ -1070,8 +1055,8 @@ private = true category = "RiseDev - Check" description = "Run cargo hakari check and attempt to fix" install_crate = { min_version = "0.9.24", crate_name = "cargo-hakari", binary = "cargo", test_arg = [ - "hakari", - "--help", + "hakari", + "--help", ], install_command = "binstall" } script = """ #!/usr/bin/env bash @@ -1088,8 +1073,8 @@ private = true category = "RiseDev - Check" description = "Run cargo sort check and attempt to fix" install_crate = { min_version = "1.0.9", crate_name = "cargo-sort", binary = "cargo", test_arg = [ - "sort", - "--help", + "sort", + "--help", ], install_command = "binstall" } script = """ #!/usr/bin/env bash @@ -1151,7 +1136,7 @@ private = true category = "RiseDev - Check" description = "Run cargo typos-cli check" install_crate = { min_version = "1.20.4", crate_name = "typos-cli", binary = "typos", test_arg = [ - "--help", + "--help", ], install_command = "binstall" } script = """ #!/usr/bin/env bash @@ -1167,8 +1152,8 @@ category = "RiseDev - Check" description = "Check unused dependencies" env = { RUSTFLAGS = "--cfg tokio_unstable" } install_crate = { min_version = "0.1.35", crate_name = "cargo-udeps", binary = "cargo", test_arg = [ - "udeps", - "--help", + "udeps", + "--help", ], install_command = "binstall" } script = """ #!/usr/bin/env bash @@ -1195,13 +1180,13 @@ scripts/check/check-trailing-spaces.sh --fix [tasks.check-fast] category = "RiseDev - Check" dependencies = [ - "warn-on-missing-tools", - # Disable hakari until we make sure it's useful - # "check-hakari", - "check-dep-sort", - "check-fmt", - "check-trailing-spaces", - "check-typos", + "warn-on-missing-tools", + # Disable hakari until we make sure it's useful + # "check-hakari", + "check-dep-sort", + "check-fmt", + "check-trailing-spaces", + "check-typos", ] description = "Perform part of the pre-CI checks that are fast to run" @@ -1222,10 +1207,10 @@ alias = "check" [tasks.check-fix] category = "RiseDev - Check" dependencies = [ - "warn-on-missing-tools", - "check-fast", - "check-clippy-fix", - "check-java-fix", + "warn-on-missing-tools", + "check-fast", + "check-clippy-fix", + "check-java-fix", ] script = """ #!/usr/bin/env bash @@ -1308,17 +1293,30 @@ echo "All processes has exited." """ [tasks.slt] -env = { SLT_HOST = "${RISEDEV_RW_FRONTEND_LISTEN_ADDRESS}", SLT_PORT = "${RISEDEV_RW_FRONTEND_PORT}", SLT_DB = "dev" } category = "RiseDev - Test - SQLLogicTest" -install_crate = { version = "0.20.1", crate_name = "sqllogictest-bin", binary = "sqllogictest", test_arg = [ - "--help", +install_crate = { min_version = "0.21.0", crate_name = "sqllogictest-bin", binary = "sqllogictest", test_arg = [ + "--help", ], install_command = "binstall" } -dependencies = ["check-risedev-env-file"] -env_files = ["${PREFIX_CONFIG}/risedev-env"] +dependencies = ["check-and-load-risedev-env-file"] command = "sqllogictest" args = ["${@}"] description = "🌟 Run SQLLogicTest" +[tasks.slt.env] +SLT_HOST = "${RISEDEV_RW_FRONTEND_LISTEN_ADDRESS}" +SLT_PORT = "${RISEDEV_RW_FRONTEND_PORT}" +SLT_DB = "dev" +PATH = "${PWD}/e2e_test/commands:${PATH}" +RUST_BACKTRACE = "0" # slt backtrace is useless +RUST_LOG = "sqllogictest=warn,sqllogictest::system_command=info" + +[tasks.slt-clean] +category = "RiseDev - Test - SQLLogicTest" +dependencies = ["clean-kafka", "reset-rw"] +description = "Run SQLLogicTest with a clean environment" +run_task = "slt" +args = ["${@}"] + [tasks.slt-streaming] category = "RiseDev - Test - SQLLogicTest" extend = "slt" @@ -1420,7 +1418,7 @@ UPDATE_EXPECT=1 cargo test -p risingwave_connector tests::test_with_options_yaml [tasks.backwards-compat-test] category = "RiseDev - Test - Backwards Compatibility Test" description = "Run backwards compatibility test" -script = "./backwards-compat-tests/scripts/run_local.sh" +script = "./e2e_test/backwards-compat-tests/scripts/run_local.sh" # For debugging. # To show the env for a specific task, use `run_task = "show-env"` for that task. @@ -1433,3 +1431,15 @@ script = """ vars = dump_variables echo ${vars} """ + +[tasks.show-risedev-env] +description = "Show risedev-env environment variables" +dependencies = ["check-risedev-env-file"] +script = ''' +#!/usr/bin/env bash +set -euo pipefail + +cat ${PREFIX_CONFIG}/risedev-env +echo "Hint: To load the environment variables into the shell, you may run:" +echo "$(tput setaf 4)\set -a; source ${PREFIX_CONFIG}/risedev-env; set +a$(tput sgr0)" +''' diff --git a/README.md b/README.md index e59af1a0395b4..156b53060313d 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ RisingWave is a Postgres-compatible streaming database engineered to provide the simplest and most cost-efficient approach for processing, analyzing, and managing real-time event streaming data. -![RisingWave](https://github.com/risingwavelabs/risingwave-docs/blob/main/docs/images/new_archi_grey.png) +![RisingWave](https://github.com/risingwavelabs/risingwave/assets/41638002/10c44404-f78b-43ce-bbd9-3646690acc59) + ## Try it out in 60 seconds @@ -70,6 +71,9 @@ Then follow the prompts to start and connect to RisingWave. To learn about other installation options, such as using a Docker image, see [Quick Start](https://docs.risingwave.com/docs/current/get-started/). +> Please note: RisingWave uses [Scarf](https://scarf.sh/) to collect anonymized installation analytics. These analytics help support us understand and improve the distribution of our package. +> The privacy policy of Scarf is available at [https://about.scarf.sh/privacy-policy](https://about.scarf.sh/privacy-policy). + ## Production deployments [**RisingWave Cloud**](https://cloud.risingwave.com) offers the easiest way to run RisingWave in production, with a _forever-free_ developer tier. diff --git a/ci/Dockerfile b/ci/Dockerfile index cffa1a026be3e..569b3f9a4b675 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -11,8 +11,8 @@ ENV LANG en_US.utf8 # Use AWS ubuntu mirror RUN sed -i 's|http://archive.ubuntu.com/ubuntu|http://us-east-2.ec2.archive.ubuntu.com/ubuntu/|g' /etc/apt/sources.list RUN apt-get update -yy && \ - DEBIAN_FRONTEND=noninteractive apt-get -y install make build-essential cmake protobuf-compiler curl parallel python3 python3-pip python3-venv software-properties-common \ - openssl libssl-dev libsasl2-dev libcurl4-openssl-dev pkg-config bash openjdk-11-jdk wget unzip git tmux lld postgresql-client kcat netcat-openbsd mysql-client \ + DEBIAN_FRONTEND=noninteractive apt-get -y install sudo make build-essential cmake protobuf-compiler curl parallel python3 python3-pip python3-venv software-properties-common \ + openssl libssl-dev libsasl2-dev libcurl4-openssl-dev pkg-config bash openjdk-17-jdk wget unzip git tmux lld postgresql-client kcat netcat-openbsd mysql-client \ maven zstd libzstd-dev locales \ python3.12 python3.12-dev \ && rm -rf /var/lib/{apt,dpkg,cache,log}/ @@ -70,14 +70,14 @@ ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash RUN cargo binstall -y --no-symlinks cargo-llvm-cov cargo-nextest cargo-hakari cargo-sort cargo-cache cargo-audit \ cargo-make@0.37.9 \ - sqllogictest-bin@0.20.1 \ + sqllogictest-bin@0.21.0 \ sccache@0.7.4 \ && cargo cache -a \ && rm -rf "/root/.cargo/registry/index" \ && rm -rf "/root/.cargo/registry/cache" \ && rm -rf "/root/.cargo/git/db" -RUN cargo install cargo-dylint@2.6.0 dylint-link@2.6.0 -RUN cargo uninstall cargo-binstall cargo-cache +RUN cargo install cargo-dylint@3.1.0 dylint-link@3.1.0 +RUN cargo uninstall cargo-cache # install risedev COPY < /dev/null; then echo "--- upload risingwave standalone docker compose logs" docker-compose -f "$COMPOSE_FILE_PATH" logs risingwave-standalone > risingwave-standalone.log @@ -19,4 +18,7 @@ if [ $BUILDKITE_COMMAND_EXIT_STATUS -ne 0 ]; then docker-compose -f "$COMPOSE_FILE_PATH" logs meta-node-0 > risingwave-meta.log fi buildkite-agent artifact upload "./risingwave-*.log" -fi \ No newline at end of file +fi + +echo "--- Docker clean" +python3 integration_tests/scripts/clean_demos.py --case "${INTEGRATION_TEST_CASE}" \ No newline at end of file diff --git a/ci/risedev-components.ci.benchmark.env b/ci/risedev-components.ci.benchmark.env index 67171fe10bc28..5bb6c4abddb56 100644 --- a/ci/risedev-components.ci.benchmark.env +++ b/ci/risedev-components.ci.benchmark.env @@ -1,7 +1,7 @@ RISEDEV_CONFIGURED=true ENABLE_MINIO=true ENABLE_ETCD=true -ENABLE_KAFKA=true ENABLE_BUILD_RUST=true ENABLE_RELEASE_PROFILE=true ENABLE_PROMETHEUS_GRAFANA=true +RW_TELEMETRY_TYPE=test diff --git a/ci/risedev-components.ci.env b/ci/risedev-components.ci.env index 7157083eb871a..0430207739145 100644 --- a/ci/risedev-components.ci.env +++ b/ci/risedev-components.ci.env @@ -1,3 +1,4 @@ RISEDEV_CONFIGURED=true ENABLE_MINIO=true ENABLE_ETCD=true +RW_TELEMETRY_TYPE=test diff --git a/ci/risedev-components.ci.source.env b/ci/risedev-components.ci.source.env index 255b873eab94d..88d76b3df8e98 100644 --- a/ci/risedev-components.ci.source.env +++ b/ci/risedev-components.ci.source.env @@ -1,5 +1,5 @@ RISEDEV_CONFIGURED=true ENABLE_MINIO=true ENABLE_ETCD=true -ENABLE_KAFKA=true ENABLE_PUBSUB=true +RW_TELEMETRY_TYPE=test diff --git a/ci/rust-toolchain b/ci/rust-toolchain index b1f2df70a8d0f..92ef4a5ff27be 100644 --- a/ci/rust-toolchain +++ b/ci/rust-toolchain @@ -4,4 +4,4 @@ # 3. (optional) **follow the instructions in lints/README.md** to update the toolchain and dependencies for lints [toolchain] -channel = "nightly-2023-12-26" +channel = "nightly-2024-03-12" diff --git a/ci/scripts/backwards-compat-test.sh b/ci/scripts/backwards-compat-test.sh index d539748a23fd9..90a01f57a51cc 100755 --- a/ci/scripts/backwards-compat-test.sh +++ b/ci/scripts/backwards-compat-test.sh @@ -32,15 +32,17 @@ else exit 1 fi -source backwards-compat-tests/scripts/utils.sh +source e2e_test/backwards-compat-tests/scripts/utils.sh ################################### Main configure_rw() { VERSION="$1" +ENABLE_BUILD="$2" echo "--- Setting up cluster config" -cat < risedev-profiles.user.yml + if version_le "$VERSION" "1.8.9"; then + cat < risedev-profiles.user.yml full-without-monitoring: steps: - use: minio @@ -49,19 +51,31 @@ full-without-monitoring: - use: compute-node - use: frontend - use: compactor - - use: zookeeper - - use: kafka EOF + else + # For versions >= 1.9.0, the default config will default to sql backend, + # breaking backwards compat, so we must specify meta-backend: etcd + cat < risedev-profiles.user.yml +full-without-monitoring: + steps: + - use: minio + - use: etcd + - use: meta-node + meta-backend: etcd + - use: compute-node + - use: frontend + - use: compactor +EOF + fi cat < risedev-components.user.env RISEDEV_CONFIGURED=true ENABLE_MINIO=true ENABLE_ETCD=true -ENABLE_KAFKA=true -# Fetch risingwave binary from release. -ENABLE_BUILD_RUST=false +# Whether to build or directly fetch binary from release. +ENABLE_BUILD_RUST=$ENABLE_BUILD # Use target/debug for simplicity. ENABLE_RELEASE_PROFILE=false @@ -73,36 +87,6 @@ if version_le "${VERSION:-}" "1.8.0" ; then fi } -configure_rw_build() { -echo "--- Setting up cluster config" -cat < risedev-profiles.user.yml -full-without-monitoring: - steps: - - use: minio - - use: etcd - - use: meta-node - - use: compute-node - - use: frontend - - use: compactor - - use: zookeeper - - use: kafka -EOF - -cat < risedev-components.user.env -RISEDEV_CONFIGURED=true - -ENABLE_MINIO=true -ENABLE_ETCD=true -ENABLE_KAFKA=true - -# Make sure that it builds -ENABLE_BUILD_RUST=true - -# Use target/debug for simplicity. -ENABLE_RELEASE_PROFILE=false -EOF -} - setup_old_cluster() { echo "--- Build risedev for $OLD_VERSION, it may not be backwards compatible" git config --global --add safe.directory /risingwave @@ -115,7 +99,7 @@ setup_old_cluster() { if [[ "$?" -ne 0 ]]; then set -e echo "Failed to download ${OLD_VERSION} from github releases, build from source later during \`risedev d\`" - configure_rw_build + configure_rw "$OLD_VERSION" true else set -e tar -xvf risingwave-v"${OLD_VERSION}"-x86_64-unknown-linux.tar.gz @@ -123,13 +107,13 @@ setup_old_cluster() { echo "--- Start cluster on tag $OLD_VERSION" git config --global --add safe.directory /risingwave - configure_rw "$OLD_VERSION" + configure_rw "$OLD_VERSION" false fi } setup_new_cluster() { echo "--- Setup Risingwave @ $RW_COMMIT" - git checkout - + git checkout "$RW_COMMIT" download_and_prepare_rw "$profile" common # Make sure we always start w/o old config rm -r .risingwave/config @@ -148,8 +132,8 @@ main() { # Assume we use the latest version, so we just set to some large number. # The current $NEW_VERSION as of this change is 1.7.0, so we can't use that. # See: https://github.com/risingwavelabs/risingwave/pull/15448 - configure_rw "99.99.99" + configure_rw "99.99.99" false validate_new_cluster "$NEW_VERSION" } -main \ No newline at end of file +main diff --git a/ci/scripts/build-other.sh b/ci/scripts/build-other.sh index 2311e5164fe74..65c50462f97a0 100755 --- a/ci/scripts/build-other.sh +++ b/ci/scripts/build-other.sh @@ -16,9 +16,13 @@ cd java mvn -B package -Dmaven.test.skip=true mvn -B install -Dmaven.test.skip=true --pl java-binding-integration-test --am mvn dependency:copy-dependencies --no-transfer-progress --pl java-binding-integration-test -mvn -B test --pl udf cd .. +echo "--- Build Java UDF" +cd e2e_test/udf/java +mvn -B package +cd ../../.. + echo "--- Build rust binary for java binding integration test" cargo build -p risingwave_java_binding --bin data-chunk-payload-generator --bin data-chunk-payload-convert-generator @@ -30,9 +34,9 @@ tar --zstd -cf java-binding-integration-test.tar.zst bin java/java-binding-integ echo "--- Upload Java artifacts" cp java/connector-node/assembly/target/risingwave-connector-1.0.0.tar.gz ./risingwave-connector.tar.gz -cp java/udf-example/target/risingwave-udf-example.jar ./risingwave-udf-example.jar +cp e2e_test/udf/java/target/risingwave-udf-example.jar ./udf.jar cp e2e_test/udf/wasm/target/wasm32-wasi/release/udf.wasm udf.wasm buildkite-agent artifact upload ./risingwave-connector.tar.gz -buildkite-agent artifact upload ./risingwave-udf-example.jar buildkite-agent artifact upload ./java-binding-integration-test.tar.zst +buildkite-agent artifact upload ./udf.jar buildkite-agent artifact upload ./udf.wasm diff --git a/ci/scripts/build-simulation.sh b/ci/scripts/build-simulation.sh index c2af5b0f3b7bc..7d8f09c5ca923 100755 --- a/ci/scripts/build-simulation.sh +++ b/ci/scripts/build-simulation.sh @@ -9,7 +9,7 @@ echo "--- Generate RiseDev CI config" cp ci/risedev-components.ci.env risedev-components.user.env echo "--- Build deterministic simulation e2e test runner" -risedev sslt-build-all --profile ci-sim +risedev sslt-build-all --profile ci-sim --timings echo "--- Show sccache stats" sccache --show-stats @@ -25,6 +25,7 @@ sccache --zero-stats echo "--- Upload artifacts" mv target/sim/ci-sim/risingwave_simulation ./risingwave_simulation tar --zstd -cvf risingwave_simulation.tar.zst risingwave_simulation +buildkite-agent artifact upload target/sim/cargo-timings/cargo-timing.html artifacts=(risingwave_simulation.tar.zst simulation-it-test.tar.zst) echo -n "${artifacts[*]}" | parallel -d ' ' "buildkite-agent artifact upload ./{}" diff --git a/ci/scripts/build.sh b/ci/scripts/build.sh index f50c5a794ec8d..f595bede10d24 100755 --- a/ci/scripts/build.sh +++ b/ci/scripts/build.sh @@ -44,6 +44,7 @@ if [[ "$profile" == "ci-dev" ]]; then RISINGWAVE_FEATURE_FLAGS=(--features rw-dynamic-link --no-default-features) else RISINGWAVE_FEATURE_FLAGS=(--features rw-static-link) + export OPENSSL_STATIC=1 fi cargo build \ @@ -54,9 +55,9 @@ cargo build \ -p risingwave_compaction_test \ -p risingwave_e2e_extended_mode_test \ "${RISINGWAVE_FEATURE_FLAGS[@]}" \ - --features embedded-deno-udf \ - --features embedded-python-udf \ - --profile "$profile" + --features all-udf \ + --profile "$profile" \ + --timings artifacts=(risingwave sqlsmith compaction-test risingwave_regress_test risingwave_e2e_extended_mode_test risedev-dev delete-range-test) @@ -66,6 +67,7 @@ ldd target/"$profile"/risingwave echo "--- Upload artifacts" echo -n "${artifacts[*]}" | parallel -d ' ' "mv target/$profile/{} ./{}-$profile && compress-and-upload-artifact ./{}-$profile" +buildkite-agent artifact upload target/cargo-timings/cargo-timing.html # This magically makes it faster to exit the docker rm -rf target diff --git a/ci/scripts/check-dylint.sh b/ci/scripts/check-dylint.sh index 77dc5dbff71f0..8dbd9e397757c 100755 --- a/ci/scripts/check-dylint.sh +++ b/ci/scripts/check-dylint.sh @@ -6,6 +6,15 @@ set -euo pipefail source ci/scripts/common.sh unset RUSTC_WRAPPER # disable sccache, see https://github.com/mozilla/sccache/issues/861 +echo "--- List all available lints" +output=$(cargo dylint list) +if [ -z "$output" ]; then + echo "ERROR: No lints detected. There might be an issue with the configuration of the lints crate." + exit 1 +else + echo "$output" +fi + echo "--- Run dylint check (dev, all features)" # Instead of `-D warnings`, we only deny warnings from our own lints. This is because... # - Warnings from `check` or `clippy` are already checked in `check.sh`. diff --git a/ci/scripts/check.sh b/ci/scripts/check.sh index f52a1b4fd3e8a..b0d0b3e678ae8 100755 --- a/ci/scripts/check.sh +++ b/ci/scripts/check.sh @@ -28,11 +28,11 @@ sccache --show-stats sccache --zero-stats echo "--- Run clippy check (release)" -cargo clippy --release --all-targets --features "rw-static-link" --locked -- -D warnings +OPENSSL_STATIC=1 cargo clippy --release --all-targets --features "rw-static-link" --locked -- -D warnings echo "--- Run cargo check on building the release binary (release)" -cargo check -p risingwave_cmd_all --features "rw-static-link" --profile release -cargo check -p risingwave_cmd --bin risectl --features "rw-static-link" --profile release +OPENSSL_STATIC=1 cargo check -p risingwave_cmd_all --features "rw-static-link" --profile release +OPENSSL_STATIC=1 cargo check -p risingwave_cmd --bin risectl --features "rw-static-link" --profile release echo "--- Show sccache stats" sccache --show-stats diff --git a/ci/scripts/common.sh b/ci/scripts/common.sh index d99066cb3d5e2..3b31afeef253d 100755 --- a/ci/scripts/common.sh +++ b/ci/scripts/common.sh @@ -12,8 +12,9 @@ export CARGO_INCREMENTAL=0 export CARGO_MAKE_PRINT_TIME_SUMMARY=true export MINIO_DOWNLOAD_BIN=https://rw-ci-deps-dist.s3.amazonaws.com/minio export MCLI_DOWNLOAD_BIN=https://rw-ci-deps-dist.s3.amazonaws.com/mc -export GCLOUD_DOWNLOAD_TGZ=https://rw-ci-deps-dist.s3.amazonaws.com/google-cloud-cli-406.0.0-linux-x86_64.tar.gz +export GCLOUD_DOWNLOAD_TGZ=https://rw-ci-deps-dist.s3.amazonaws.com/google-cloud-cli-475.0.0-linux-x86_64.tar.gz export NEXTEST_HIDE_PROGRESS_BAR=true +export RW_TELEMETRY_TYPE=test unset LANG if [ -n "${BUILDKITE_COMMIT:-}" ]; then export GIT_SHA=$BUILDKITE_COMMIT diff --git a/ci/scripts/connector-node-integration-test.sh b/ci/scripts/connector-node-integration-test.sh index 519215584abb9..8853243b66805 100755 --- a/ci/scripts/connector-node-integration-test.sh +++ b/ci/scripts/connector-node-integration-test.sh @@ -30,8 +30,6 @@ shift $((OPTIND -1)) RISINGWAVE_ROOT=${PWD} echo "--- install java" -apt install sudo -y && apt-get update - if [ "$VERSION" = "11" ]; then echo "The test imgae default java version is 11, no need to install" else @@ -92,15 +90,15 @@ export PYTHONPATH=proto echo "--- running streamchunk data format integration tests" cd "${RISINGWAVE_ROOT}"/java/connector-node/python-client -if python3 integration_tests.py --stream_chunk_format_test --input_binary_file="./data/stream_chunk_data" --data_format_use_json=False; then +if python3 integration_tests.py --stream_chunk_format_test --input_binary_file="./data/stream_chunk_data"; then echo "StreamChunk data format test passed" else echo "StreamChunk data format test failed" exit 1 fi -sink_input_feature=("--input_binary_file=./data/sink_input --data_format_use_json=False") -upsert_sink_input_feature=("--input_binary_file=./data/upsert_sink_input --data_format_use_json=False") +sink_input_feature=("--input_binary_file=./data/sink_input") +upsert_sink_input_feature=("--input_binary_file=./data/upsert_sink_input") type=("StreamChunk format") ${MC_PATH} mb minio/bucket diff --git a/ci/scripts/deterministic-e2e-test.sh b/ci/scripts/deterministic-e2e-test.sh index cb23f1cd7f247..c561978e428aa 100755 --- a/ci/scripts/deterministic-e2e-test.sh +++ b/ci/scripts/deterministic-e2e-test.sh @@ -49,7 +49,7 @@ echo "--- deterministic simulation e2e, ci-3cn-2fe, parallel, batch" seq "$TEST_NUM" | parallel MADSIM_TEST_SEED={} './risingwave_simulation -j 16 ./e2e_test/batch/\*\*/\*.slt 2> $LOGDIR/parallel-batch-{}.log && rm $LOGDIR/parallel-batch-{}.log' echo "--- deterministic simulation e2e, ci-3cn-2fe, fuzzing (pre-generated-queries)" -timeout 10m seq 64 | parallel MADSIM_TEST_SEED={} './risingwave_simulation --run-sqlsmith-queries ./src/tests/sqlsmith/tests/sqlsmith-query-snapshots/{} 2> $LOGDIR/fuzzing-{}.log && rm $LOGDIR/fuzzing-{}.log' +timeout 10m seq 64 | parallel MADSIM_TEST_SEED={} RUST_MIN_STACK=4194304 './risingwave_simulation --run-sqlsmith-queries ./src/tests/sqlsmith/tests/sqlsmith-query-snapshots/{} 2> $LOGDIR/fuzzing-{}.log && rm $LOGDIR/fuzzing-{}.log' echo "--- deterministic simulation e2e, ci-3cn-2fe, e2e extended mode test" seq "$TEST_NUM" | parallel MADSIM_TEST_SEED={} './risingwave_simulation -e 2> $LOGDIR/extended-{}.log && rm $LOGDIR/extended-{}.log' diff --git a/ci/scripts/docker.sh b/ci/scripts/docker.sh index f7936f9987d70..a909b7c6a37c5 100755 --- a/ci/scripts/docker.sh +++ b/ci/scripts/docker.sh @@ -13,6 +13,14 @@ echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin echo "--- dockerhub login" echo "$DOCKER_TOKEN" | docker login -u "risingwavelabs" --password-stdin +if [[ -n "${ORIGINAL_IMAGE_TAG+x}" ]] && [[ -n "${NEW_IMAGE_TAG+x}" ]]; then + echo "--- retag docker image" + docker pull ${ghcraddr}:${ORIGINAL_IMAGE_TAG} + docker tag ${ghcraddr}:${ORIGINAL_IMAGE_TAG} ${ghcraddr}:${NEW_IMAGE_TAG}-${arch} + docker push ${ghcraddr}:${NEW_IMAGE_TAG}-${arch} + exit 0 +fi + # Build RisingWave docker image ${BUILDKITE_COMMIT}-${arch} echo "--- docker build and tag" docker buildx create \ diff --git a/ci/scripts/e2e-cassandra-sink-test.sh b/ci/scripts/e2e-cassandra-sink-test.sh index cae03843c4703..41ff0e97190d0 100755 --- a/ci/scripts/e2e-cassandra-sink-test.sh +++ b/ci/scripts/e2e-cassandra-sink-test.sh @@ -33,7 +33,8 @@ tar xf ./risingwave-connector.tar.gz -C ./connector-node echo "--- starting risingwave cluster" risedev ci-start ci-sink-test -sleep 1 +# Wait cassandra server to start +sleep 40 echo "--- create cassandra table" curl https://downloads.apache.org/cassandra/4.1.3/apache-cassandra-4.1.3-bin.tar.gz --output apache-cassandra-4.1.3-bin.tar.gz @@ -47,7 +48,7 @@ pip3 install --break-system-packages cassandra-driver cd apache-cassandra-4.1.3/bin export CQLSH_HOST=cassandra-server export CQLSH_PORT=9042 -./cqlsh -e "CREATE KEYSPACE demo WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};use demo; +./cqlsh --request-timeout=20 -e "CREATE KEYSPACE demo WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};use demo; CREATE table demo_bhv_table(v1 int primary key,v2 smallint,v3 bigint,v4 float,v5 double,v6 text,v7 date,v8 timestamp,v9 boolean);" echo "--- testing sinks" @@ -55,7 +56,7 @@ cd ../../ sqllogictest -p 4566 -d dev './e2e_test/sink/cassandra_sink.slt' sleep 1 cd apache-cassandra-4.1.3/bin -./cqlsh -e "COPY demo.demo_bhv_table TO './query_result.csv' WITH HEADER = false AND ENCODING = 'UTF-8';" +./cqlsh --request-timeout=20 -e "COPY demo.demo_bhv_table TO './query_result.csv' WITH HEADER = false AND ENCODING = 'UTF-8';" if cat ./query_result.csv | awk -F "," '{ exit !($1 == 1 && $2 == 1 && $3 == 1 && $4 == 1.1 && $5 == 1.2 && $6 == "test" && $7 == "2013-01-01" && $8 == "2013-01-01 01:01:01.000+0000" && $9 == "False\r"); }'; then diff --git a/ci/scripts/e2e-kafka-sink-test.sh b/ci/scripts/e2e-kafka-sink-test.sh index 1dd7a27831d49..7cab1ae1f76f7 100755 --- a/ci/scripts/e2e-kafka-sink-test.sh +++ b/ci/scripts/e2e-kafka-sink-test.sh @@ -3,19 +3,22 @@ # Exits as soon as any line fails. set -euo pipefail -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only --create > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert --create > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert-schema --create > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-debezium --create > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-without-snapshot --create > /dev/null 2>&1 +export RPK_BROKERS="message_queue:29092" + +rpk topic create test-rw-sink-append-only +rpk topic create test-rw-sink-upsert +rpk topic create test-rw-sink-upsert-schema +rpk topic create test-rw-sink-debezium +rpk topic create test-rw-sink-without-snapshot +rpk topic create test-rw-sink-text-key-id sqllogictest -p 4566 -d dev 'e2e_test/sink/kafka/create_sink.slt' sleep 2 # test append-only kafka sink echo "testing append-only kafka sink" -diff ./e2e_test/sink/kafka/append_only1.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only --from-beginning --max-messages 10 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/append_only1.result \ +<((rpk topic consume test-rw-sink-append-only --offset start --format '%v\n' --num 10 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for append-only sink is not as expected." exit 1 @@ -23,8 +26,8 @@ fi # test upsert kafka sink echo "testing upsert kafka sink" -diff ./e2e_test/sink/kafka/upsert1.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert --from-beginning --property print.key=true --max-messages 10 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/upsert1.result \ +<((rpk topic consume test-rw-sink-upsert --offset start --format '%k\t%v\n' --num 10 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for upsert sink is not as expected." exit 1 @@ -32,8 +35,8 @@ fi # test upsert kafka sink with schema echo "testing upsert kafka sink with schema" -diff ./e2e_test/sink/kafka/upsert_schema1.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert-schema --from-beginning --property print.key=true --max-messages 10 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/upsert_schema1.result \ +<((rpk topic consume test-rw-sink-upsert-schema --offset start --format '%k\t%v\n' --num 10 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for upsert sink with schema is not as expected." exit 1 @@ -41,7 +44,7 @@ fi # test debezium kafka sink echo "testing debezium kafka sink" -(./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-debezium --property print.key=true --from-beginning --max-messages 10 | sort) > ./e2e_test/sink/kafka/debezium1.tmp.result 2> /dev/null +(rpk topic consume test-rw-sink-debezium --offset start --format '%k\t%v\n' --num 10 | sort) > ./e2e_test/sink/kafka/debezium1.tmp.result 2> /dev/null python3 e2e_test/sink/kafka/debezium.py e2e_test/sink/kafka/debezium1.result e2e_test/sink/kafka/debezium1.tmp.result if [ $? -ne 0 ]; then echo "The output for debezium sink is not as expected." @@ -57,8 +60,8 @@ psql -h localhost -p 4566 -d dev -U root -c "update t_kafka set v_varchar = '', # test append-only kafka sink after update echo "testing append-only kafka sink after updating data" -diff ./e2e_test/sink/kafka/append_only2.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only --from-beginning --max-messages 11 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/append_only2.result \ +<((rpk topic consume test-rw-sink-append-only --offset start --format '%v\n' --num 11 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for append-only sink after update is not as expected." exit 1 @@ -66,8 +69,8 @@ fi # test upsert kafka sink after update echo "testing upsert kafka sink after updating data" -diff ./e2e_test/sink/kafka/upsert2.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert --from-beginning --property print.key=true --max-messages 11 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/upsert2.result \ +<((rpk topic consume test-rw-sink-upsert --offset start --format '%k\t%v\n' --num 11 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for upsert sink after update is not as expected." exit 1 @@ -75,8 +78,8 @@ fi # test upsert kafka sink with schema after update echo "testing upsert kafka sink with schema after updating data" -diff ./e2e_test/sink/kafka/upsert_schema2.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert-schema --from-beginning --property print.key=true --max-messages 11 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/upsert_schema2.result \ +<((rpk topic consume test-rw-sink-upsert-schema --offset start --format '%k\t%v\n' --num 11 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for upsert sink with schema is not as expected." exit 1 @@ -84,7 +87,7 @@ fi # test debezium kafka sink after update echo "testing debezium kafka sink after updating data" -(./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-debezium --property print.key=true --from-beginning --max-messages 11 | sort) > ./e2e_test/sink/kafka/debezium2.tmp.result 2> /dev/null +(rpk topic consume test-rw-sink-debezium --offset start --format '%k\t%v\n' --num 11 | sort) > ./e2e_test/sink/kafka/debezium2.tmp.result 2> /dev/null python3 e2e_test/sink/kafka/debezium.py e2e_test/sink/kafka/debezium2.result e2e_test/sink/kafka/debezium2.tmp.result if [ $? -ne 0 ]; then echo "The output for debezium sink after update is not as expected." @@ -96,8 +99,8 @@ fi # test without-snapshot kafka sink echo "testing without-snapshot kafka sink" -diff ./e2e_test/sink/kafka/without_snapshot.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-without-snapshot --from-beginning --max-messages 3 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/without_snapshot.result \ +<((rpk topic consume test-rw-sink-without-snapshot --offset start --format '%v\n' --num 3 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for append-only sink is not as expected." exit 1 @@ -109,8 +112,8 @@ psql -h localhost -p 4566 -d dev -U root -c "delete from t_kafka where id = 1;" # test upsert kafka sink after delete echo "testing upsert kafka sink after deleting data" -diff ./e2e_test/sink/kafka/upsert3.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert --from-beginning --property print.key=true --max-messages 12 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/upsert3.result \ +<((rpk topic consume test-rw-sink-upsert --offset start --format '%k\t%v\n' --num 12 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for upsert sink after update is not as expected." exit 1 @@ -118,8 +121,8 @@ fi # test upsert kafka sink with schema after delete echo "testing upsert kafka sink with schema after deleting data" -diff ./e2e_test/sink/kafka/upsert_schema3.result \ -<((./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert-schema --from-beginning --property print.key=true --max-messages 12 | sort) 2> /dev/null) +diff -b ./e2e_test/sink/kafka/upsert_schema3.result \ +<((rpk topic consume test-rw-sink-upsert-schema --offset start --format '%k\t%v\n' --num 12 | sort) 2> /dev/null) if [ $? -ne 0 ]; then echo "The output for upsert sink with schema is not as expected." exit 1 @@ -127,7 +130,7 @@ fi # test debezium kafka sink after delete echo "testing debezium kafka sink after deleting data" -(./.risingwave/bin/kafka/bin/kafka-console-consumer.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-debezium --property print.key=true --from-beginning --max-messages 13 | sort) > ./e2e_test/sink/kafka/debezium3.tmp.result 2> /dev/null +(rpk topic consume test-rw-sink-debezium --offset start --format '%k\t%v\n' --num 13 | sort) > ./e2e_test/sink/kafka/debezium3.tmp.result 2> /dev/null python3 e2e_test/sink/kafka/debezium.py e2e_test/sink/kafka/debezium3.result e2e_test/sink/kafka/debezium3.tmp.result if [ $? -ne 0 ]; then echo "The output for debezium sink after delete is not as expected." @@ -138,9 +141,9 @@ else fi sqllogictest -p 4566 -d dev 'e2e_test/sink/kafka/drop_sink.slt' -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only --delete > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert --delete > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-debezium --delete > /dev/null 2>&1 +rpk topic delete test-rw-sink-append-only +rpk topic delete test-rw-sink-upsert +rpk topic delete test-rw-sink-debezium # test different encoding echo "preparing confluent schema registry" @@ -148,19 +151,19 @@ python3 -m pip install --break-system-packages requests confluent-kafka echo "testing protobuf" cp src/connector/src/test_data/proto_recursive/recursive.pb ./proto-recursive -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only-protobuf --create > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only-protobuf-csr-a --create > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only-protobuf-csr-hi --create > /dev/null 2>&1 -python3 e2e_test/sink/kafka/register_schema.py 'http://message_queue:8081' 'test-rw-sink-append-only-protobuf-csr-a-value' src/connector/src/test_data/test-index-array.proto -python3 e2e_test/sink/kafka/register_schema.py 'http://message_queue:8081' 'test-rw-sink-append-only-protobuf-csr-hi-value' src/connector/src/test_data/test-index-array.proto +rpk topic create test-rw-sink-append-only-protobuf +rpk topic create test-rw-sink-append-only-protobuf-csr-a +rpk topic create test-rw-sink-append-only-protobuf-csr-hi +python3 e2e_test/sink/kafka/register_schema.py 'http://schemaregistry:8082' 'test-rw-sink-append-only-protobuf-csr-a-value' src/connector/src/test_data/test-index-array.proto +python3 e2e_test/sink/kafka/register_schema.py 'http://schemaregistry:8082' 'test-rw-sink-append-only-protobuf-csr-hi-value' src/connector/src/test_data/test-index-array.proto sqllogictest -p 4566 -d dev 'e2e_test/sink/kafka/protobuf.slt' -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only-protobuf-csr-hi --delete > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only-protobuf-csr-a --delete > /dev/null 2>&1 -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-append-only-protobuf --delete > /dev/null 2>&1 +rpk topic delete test-rw-sink-append-only-protobuf +rpk topic delete test-rw-sink-append-only-protobuf-csr-a +rpk topic delete test-rw-sink-append-only-protobuf-csr-hi echo "testing avro" -python3 e2e_test/sink/kafka/register_schema.py 'http://message_queue:8081' 'test-rw-sink-upsert-avro-value' src/connector/src/test_data/all-types.avsc -python3 e2e_test/sink/kafka/register_schema.py 'http://message_queue:8081' 'test-rw-sink-upsert-avro-key' src/connector/src/test_data/all-types.avsc 'string_field,int32_field' -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert-avro --create > /dev/null 2>&1 +python3 e2e_test/sink/kafka/register_schema.py 'http://schemaregistry:8082' 'test-rw-sink-upsert-avro-value' src/connector/src/test_data/all-types.avsc +python3 e2e_test/sink/kafka/register_schema.py 'http://schemaregistry:8082' 'test-rw-sink-upsert-avro-key' src/connector/src/test_data/all-types.avsc 'string_field,int32_field' +rpk topic create test-rw-sink-upsert-avro sqllogictest -p 4566 -d dev 'e2e_test/sink/kafka/avro.slt' -./.risingwave/bin/kafka/bin/kafka-topics.sh --bootstrap-server message_queue:29092 --topic test-rw-sink-upsert-avro --delete > /dev/null 2>&1 +rpk topic delete test-rw-sink-upsert-avro diff --git a/ci/scripts/e2e-mongodb-sink-test.sh b/ci/scripts/e2e-mongodb-sink-test.sh new file mode 100755 index 0000000000000..6ec6e97cf0fe1 --- /dev/null +++ b/ci/scripts/e2e-mongodb-sink-test.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +# Exits as soon as any line fails. +set -euo pipefail + +source ci/scripts/common.sh + +while getopts 'p:' opt; do + case ${opt} in + p ) + profile=$OPTARG + ;; + \? ) + echo "Invalid Option: -$OPTARG" 1>&2 + exit 1 + ;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + ;; + esac +done +shift $((OPTIND -1)) + +download_and_prepare_rw "$profile" source + +echo "--- starting risingwave cluster" +cargo make ci-start ci-sink-test +sleep 1 + +# install the mongo shell +wget http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb +wget https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-amd64/mongodb-org-shell_4.4.28_amd64.deb +dpkg -i libssl1.1_1.1.1f-1ubuntu2_amd64.deb +dpkg -i mongodb-org-shell_4.4.28_amd64.deb + +echo '> ping mongodb' +echo 'db.runCommand({ping: 1})' | mongo mongodb://mongodb:27017 +echo '> rs config' +echo 'rs.conf()' | mongo mongodb://mongodb:27017 +echo '> run mongodb sink test..' + +sqllogictest -p 4566 -d dev './e2e_test/sink/mongodb_sink.slt' +sleep 1 + +append_only_result=$(mongo mongodb://mongodb:27017 --eval 'db.getSiblingDB("demo").t1.countDocuments({})' | tail -n 1) +if [ "$append_only_result" != "1" ]; then + echo "The append-only output is not as expected." + exit 1 +fi + +upsert_and_dynamic_coll_result1=$(mongo mongodb://mongodb:27017 --eval 'db.getSiblingDB("demo").t2.countDocuments({})' | tail -n 1) +if [ "$upsert_and_dynamic_coll_result1" != "1" ]; then + echo "The upsert output is not as expected." + exit 1 +fi + +upsert_and_dynamic_coll_result2=$(mongo mongodb://mongodb:27017 --eval 'db.getSiblingDB("shard_2024_01").tenant_1.countDocuments({})' | tail -n 1) +if [ "$upsert_and_dynamic_coll_result2" != "1" ]; then + echo "The upsert output is not as expected." + exit 1 +fi + +compound_pk_result=$(mongo mongodb://mongodb:27017 --eval 'db.getSiblingDB("demo").t3.countDocuments({})' | tail -n 1) +if [ "$compound_pk_result" != "1" ]; then + echo "The upsert output is not as expected." + exit 1 +fi + +echo "Mongodb sink check passed" + +echo "--- Kill cluster" +risedev ci-kill \ No newline at end of file diff --git a/ci/scripts/e2e-redis-sink-test.sh b/ci/scripts/e2e-redis-sink-test.sh index c7567dd51ed04..45e578b9268af 100755 --- a/ci/scripts/e2e-redis-sink-test.sh +++ b/ci/scripts/e2e-redis-sink-test.sh @@ -56,7 +56,7 @@ sqllogictest -p 4566 -d dev './e2e_test/sink/redis_cluster_sink.slt' redis-cli -c --cluster call 127.0.0.1:7000 keys \* >> ./query_result_1.txt line_count=$(wc -l < query_result_1.txt) -if [ "$line_count" -eq 4 ]; then +if [ "$line_count" -eq 16 ]; then echo "Redis sink check passed" else cat ./query_result_1.txt diff --git a/ci/scripts/e2e-sink-test.sh b/ci/scripts/e2e-sink-test.sh index 5125ddedfdf8e..eaee563c7a992 100755 --- a/ci/scripts/e2e-sink-test.sh +++ b/ci/scripts/e2e-sink-test.sh @@ -45,7 +45,7 @@ mysql --host=mysql --port=3306 -u root -p123456 test < ./e2e_test/sink/remote/my echo "--- preparing postgresql" # set up PG sink destination -apt-get -y install postgresql-client +apt-get -y install postgresql-client jq export PGPASSWORD=postgres psql -h db -U postgres -c "CREATE ROLE test LOGIN SUPERUSER PASSWORD 'connector';" createdb -h db -U postgres test diff --git a/ci/scripts/e2e-source-test.sh b/ci/scripts/e2e-source-test.sh index 8a683f56b8550..c4b4713af81cc 100755 --- a/ci/scripts/e2e-source-test.sh +++ b/ci/scripts/e2e-source-test.sh @@ -31,6 +31,17 @@ buildkite-agent artifact download risingwave-connector.tar.gz ./ mkdir ./connector-node tar xf ./risingwave-connector.tar.gz -C ./connector-node +echo "--- Install dependencies" +python3 -m pip install --break-system-packages requests protobuf fastavro confluent_kafka jsonschema +apt-get -y install jq + +echo "--- e2e, inline test" +RUST_LOG="debug,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ +risedev ci-start ci-inline-source-test +risedev slt './e2e_test/source_inline/**/*.slt' +echo "--- Kill cluster" +risedev ci-kill + echo "--- Prepare data" cp src/connector/src/test_data/simple-schema.avsc ./avro-simple-schema.avsc cp src/connector/src/test_data/complex-schema.avsc ./avro-complex-schema.avsc @@ -49,7 +60,7 @@ createdb psql < ./e2e_test/source/cdc/postgres_cdc.sql echo "--- starting risingwave cluster" -RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ +RUST_LOG="debug,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ risedev ci-start ci-1cn-1fe-with-recovery echo "--- mongodb cdc test" @@ -82,15 +93,6 @@ echo "--- cdc share source test" export MYSQL_HOST=mysql MYSQL_TCP_PORT=3306 MYSQL_PWD=123456 risedev slt './e2e_test/source/cdc/cdc.share_stream.slt' -# create a share source and check whether heartbeat message is received -risedev slt './e2e_test/source/cdc/cdc.create_source_job.slt' -table_id=$(psql -U root -h localhost -p 4566 -d dev -t -c "select id from rw_internal_tables where name like '%mysql_source%';" | xargs); -table_count=$(psql -U root -h localhost -p 4566 -d dev -t -c "select count(*) from rw_table(${table_id}, public);" | xargs); -if [ "$table_count" -eq 0 ]; then - echo "ERROR: internal table of cdc share source is empty!" - exit 1 -fi - echo "--- mysql & postgres load and check" risedev slt './e2e_test/source/cdc/cdc.load.slt' # wait for cdc loading @@ -133,32 +135,20 @@ risedev slt './e2e_test/source/cdc/cdc_share_stream_drop.slt' echo "--- Kill cluster" risedev ci-kill - -echo "--- e2e, ci-1cn-1fe, protobuf schema registry" export RISINGWAVE_CI=true -RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ -risedev ci-start ci-1cn-1fe -python3 -m pip install --break-system-packages requests protobuf confluent-kafka -python3 e2e_test/schema_registry/pb.py "message_queue:29092" "http://message_queue:8081" "sr_pb_test" 20 user -echo "make sure google/protobuf/source_context.proto is NOT in schema registry" -curl --silent 'http://message_queue:8081/subjects'; echo -# curl --silent --head -X GET 'http://message_queue:8081/subjects/google%2Fprotobuf%2Fsource_context.proto/versions' | grep 404 -curl --silent 'http://message_queue:8081/subjects' | grep -v 'google/protobuf/source_context.proto' -risedev slt './e2e_test/schema_registry/pb.slt' -risedev slt './e2e_test/schema_registry/alter_sr.slt' - -echo "--- Kill cluster" -risedev ci-kill echo "--- e2e, ci-kafka-plus-pubsub, kafka and pubsub source" RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ -risedev ci-start ci-pubsub-kafka +risedev ci-start ci-kafka ./scripts/source/prepare_ci_kafka.sh -cargo run --bin prepare_ci_pubsub risedev slt './e2e_test/source/basic/*.slt' risedev slt './e2e_test/source/basic/old_row_format_syntax/*.slt' risedev slt './e2e_test/source/basic/alter/kafka.slt' +echo "--- e2e, kafka alter source rate limit" +risedev slt './e2e_test/source/basic/alter/rate_limit_source_kafka.slt' +risedev slt './e2e_test/source/basic/alter/rate_limit_table_kafka.slt' + echo "--- e2e, kafka alter source" chmod +x ./scripts/source/prepare_data_after_alter.sh ./scripts/source/prepare_data_after_alter.sh 2 @@ -168,9 +158,6 @@ echo "--- e2e, kafka alter source again" ./scripts/source/prepare_data_after_alter.sh 3 risedev slt './e2e_test/source/basic/alter/kafka_after_new_data_2.slt' -echo "--- e2e, inline test" -risedev slt './e2e_test/source_inline/**/*.slt' - echo "--- Run CH-benCHmark" risedev slt './e2e_test/ch_benchmark/batch/ch_benchmark.slt' risedev slt './e2e_test/ch_benchmark/streaming/*.slt' diff --git a/ci/scripts/e2e-sqlserver-sink-test.sh b/ci/scripts/e2e-sqlserver-sink-test.sh new file mode 100755 index 0000000000000..f1f62941375ce --- /dev/null +++ b/ci/scripts/e2e-sqlserver-sink-test.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Exits as soon as any line fails. +set -euo pipefail + +source ci/scripts/common.sh + +while getopts 'p:' opt; do + case ${opt} in + p ) + profile=$OPTARG + ;; + \? ) + echo "Invalid Option: -$OPTARG" 1>&2 + exit 1 + ;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + ;; + esac +done +shift $((OPTIND -1)) + +download_and_prepare_rw "$profile" source + +echo "--- starting risingwave cluster" +risedev ci-start ci-sink-test +sleep 1 + +echo "--- create SQL Server table" +curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - +curl https://packages.microsoft.com/config/ubuntu/20.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list +apt-get update -y +ACCEPT_EULA=Y DEBIAN_FRONTEND=noninteractive apt-get install -y mssql-tools unixodbc-dev +export PATH="/opt/mssql-tools/bin/:$PATH" +sleep 2 + +sqlcmd -S sqlserver-server -U SA -P SomeTestOnly@SA -Q " +CREATE DATABASE SinkTest; +GO +USE SinkTest; +CREATE TABLE t_many_data_type ( + k1 int, k2 int, + c_boolean bit, + c_int16 smallint, + c_int32 int, + c_int64 bigint, + c_float32 float, + c_float64 float, + c_decimal decimal, + c_date date, + c_time time, + c_timestamp datetime2, + c_timestampz datetime2, + c_nvarchar nvarchar(1024), + c_varbinary varbinary(1024), +PRIMARY KEY (k1,k2)); +GO" +sleep 2 + +echo "--- testing sinks" +sqllogictest -p 4566 -d dev './e2e_test/sink/sqlserver_sink.slt' +sleep 1 +sqlcmd -S sqlserver-server -U SA -P SomeTestOnly@SA -h -1 -Q " +SELECT * FROM SinkTest.dbo.t_many_data_type; +GO" > ./query_result.txt + +mapfile -t actual < <(tr -s '[:space:]' '\n' < query_result.txt) +actual=("${actual[@]:1}") +expected=(0 0 0 NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL 1 1 0 55 55 1 1.0 1.0 1 2022-04-08 18:20:49.0000000 2022-03-13 01:00:00.0000000 2022-03-13 01:00:00.0000000 Hello World! 0xDE00BEEF 1 2 0 66 66 1 1.0 1.0 1 2022-04-08 18:20:49.0000000 2022-03-13 01:00:00.0000000 2022-03-13 01:00:00.0000000 Hello World! 0xDE00BEEF 1 4 0 2 2 1 1.0 1.0 1 2022-04-08 18:20:49.0000000 2022-03-13 01:00:00.0000000 2022-03-13 01:00:00.0000000 Hello World! 0xDE00BEEF "(4" rows "affected)") + +if [[ ${#actual[@]} -eq ${#expected[@]} && ${actual[@]} == ${expected[@]} ]]; then + echo "SQL Server sink check passed" +else + cat ./query_result.txt + echo "The output is not as expected." +fi + +echo "--- Kill cluster" +risedev ci-kill diff --git a/ci/scripts/e2e-test-parallel-for-opendal.sh b/ci/scripts/e2e-test-parallel-for-opendal.sh index 606adcf929cd7..a6a6c89e41164 100755 --- a/ci/scripts/e2e-test-parallel-for-opendal.sh +++ b/ci/scripts/e2e-test-parallel-for-opendal.sh @@ -31,7 +31,7 @@ host_args=(-h localhost -p 4565 -h localhost -p 4566 -h localhost -p 4567) echo "--- e2e, ci-3cn-3fe-opendal-fs-backend, streaming" RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ risedev ci-start ci-3cn-3fe-opendal-fs-backend -sqllogictest "${host_args[@]}" -d dev './e2e_test/streaming/**/*.slt' -j 16 --junit "parallel-opendal-fs-backend-${profile}" +sqllogictest "${host_args[@]}" -d dev './e2e_test/streaming/**/*.slt' -j 16 --junit "parallel-opendal-fs-backend-${profile}" --label "parallel" echo "--- Kill cluster Streaming" risedev ci-kill @@ -41,8 +41,8 @@ rm -rf /tmp/rw_ci echo "--- e2e, ci-3cn-3fe-opendal-fs-backend, batch" RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ risedev ci-start ci-3cn-3fe-opendal-fs-backend -sqllogictest "${host_args[@]}" -d dev './e2e_test/ddl/**/*.slt' --junit "parallel-opendal-fs-backend-ddl-${profile}" -sqllogictest "${host_args[@]}" -d dev './e2e_test/visibility_mode/*.slt' -j 16 --junit "parallel-opendal-fs-backend-batch-${profile}" +sqllogictest "${host_args[@]}" -d dev './e2e_test/ddl/**/*.slt' --junit "parallel-opendal-fs-backend-ddl-${profile}" --label "parallel" +sqllogictest "${host_args[@]}" -d dev './e2e_test/visibility_mode/*.slt' -j 16 --junit "parallel-opendal-fs-backend-batch-${profile}" --label "parallel" echo "--- Kill cluster Batch" risedev ci-kill diff --git a/ci/scripts/e2e-test-parallel-in-memory.sh b/ci/scripts/e2e-test-parallel-in-memory.sh index fcde15644c2b6..f7e6292bed54e 100755 --- a/ci/scripts/e2e-test-parallel-in-memory.sh +++ b/ci/scripts/e2e-test-parallel-in-memory.sh @@ -28,15 +28,15 @@ host_args=(-h localhost -p 4565 -h localhost -p 4566 -h localhost -p 4567) echo "--- e2e, ci-3cn-3fe-in-memory, streaming" risedev ci-start ci-3cn-3fe-in-memory sqllogictest --version -sqllogictest "${host_args[@]}" -d dev './e2e_test/streaming/**/*.slt' -j 16 --junit "parallel-in-memory-streaming-${profile}" --label in-memory +sqllogictest "${host_args[@]}" -d dev './e2e_test/streaming/**/*.slt' -j 16 --junit "parallel-in-memory-streaming-${profile}" --label "in-memory" --label "parallel" echo "--- Kill cluster" risedev ci-kill echo "--- e2e, ci-3cn-3fe-in-memory, batch" risedev ci-start ci-3cn-3fe-in-memory -sqllogictest "${host_args[@]}" -d dev './e2e_test/ddl/**/*.slt' --junit "parallel-in-memory-batch-ddl-${profile}" --label in-memory -sqllogictest "${host_args[@]}" -d dev './e2e_test/batch/**/*.slt' -j 16 --junit "parallel-in-memory-batch-${profile}" --label in-memory +sqllogictest "${host_args[@]}" -d dev './e2e_test/ddl/**/*.slt' --junit "parallel-in-memory-batch-ddl-${profile}" --label "in-memory" --label "parallel" +sqllogictest "${host_args[@]}" -d dev './e2e_test/batch/**/*.slt' -j 16 --junit "parallel-in-memory-batch-${profile}" --label "in-memory" --label "parallel" echo "--- Kill cluster" risedev ci-kill diff --git a/ci/scripts/e2e-test-parallel.sh b/ci/scripts/e2e-test-parallel.sh index 5f16a4c817871..c366ef07fc209 100755 --- a/ci/scripts/e2e-test-parallel.sh +++ b/ci/scripts/e2e-test-parallel.sh @@ -38,21 +38,21 @@ RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=i echo "--- e2e, ci-3streaming-2serving-3fe, streaming" RUST_LOG=$RUST_LOG \ risedev ci-start ci-3streaming-2serving-3fe -sqllogictest "${host_args[@]}" -d dev './e2e_test/streaming/**/*.slt' -j 16 --junit "parallel-streaming-${profile}" +sqllogictest "${host_args[@]}" -d dev './e2e_test/streaming/**/*.slt' -j 16 --junit "parallel-streaming-${profile}" --label "parallel" kill_cluster echo "--- e2e, ci-3streaming-2serving-3fe, batch" RUST_LOG=$RUST_LOG \ risedev ci-start ci-3streaming-2serving-3fe -sqllogictest "${host_args[@]}" -d dev './e2e_test/ddl/**/*.slt' --junit "parallel-batch-ddl-${profile}" -sqllogictest "${host_args[@]}" -d dev './e2e_test/visibility_mode/*.slt' -j 16 --junit "parallel-batch-${profile}" +sqllogictest "${host_args[@]}" -d dev './e2e_test/ddl/**/*.slt' --junit "parallel-batch-ddl-${profile}" --label "parallel" +sqllogictest "${host_args[@]}" -d dev './e2e_test/visibility_mode/*.slt' -j 16 --junit "parallel-batch-${profile}" --label "parallel" kill_cluster echo "--- e2e, ci-3streaming-2serving-3fe, generated" RUST_LOG=$RUST_LOG \ risedev ci-start ci-3streaming-2serving-3fe -sqllogictest "${host_args[@]}" -d dev './e2e_test/generated/**/*.slt' -j 16 --junit "parallel-generated-${profile}" +sqllogictest "${host_args[@]}" -d dev './e2e_test/generated/**/*.slt' -j 16 --junit "parallel-generated-${profile}" --label "parallel" kill_cluster diff --git a/ci/scripts/gen-integration-test-yaml.py b/ci/scripts/gen-integration-test-yaml.py index 61fd7721acc3f..c778205cfbb3e 100644 --- a/ci/scripts/gen-integration-test-yaml.py +++ b/ci/scripts/gen-integration-test-yaml.py @@ -44,6 +44,8 @@ 'presto-trino': ['json'], 'client-library': ['none'], 'kafka-cdc': ['json'], + 'pubsub': ['json'], + 'dynamodb': ['json'], } def gen_pipeline_steps(): diff --git a/ci/scripts/integration-tests.sh b/ci/scripts/integration-tests.sh index 9502c297f87ca..c686e83cd9cbd 100755 --- a/ci/scripts/integration-tests.sh +++ b/ci/scripts/integration-tests.sh @@ -25,11 +25,12 @@ shift $((OPTIND -1)) echo "export INTEGRATION_TEST_CASE=${case}" > env_vars.sh echo "~~~ clean up docker" +# shellcheck disable=SC2046 if [ $(docker ps -aq |wc -l) -gt 0 ]; then docker rm -f $(docker ps -aq) fi docker network prune -f -docker volume prune -f +docker volume prune -f -a echo "~~~ ghcr login" echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin @@ -54,8 +55,7 @@ fi echo "--- case: ${case}, format: ${format}" if [ "${case}" == "client-library" ]; then - cd integration_tests/client-library - python3 client_test.py + python3 integration_tests/client-library/client_test.py exit 0 fi @@ -70,11 +70,9 @@ export PATH=$PATH:$HOME/.local/bin echo "--- download rwctest-key" aws secretsmanager get-secret-value --secret-id "gcp-buildkite-rwctest-key" --region us-east-2 --query "SecretString" --output text >gcp-rwctest.json -cd integration_tests/scripts - echo "--- rewrite docker compose for protobuf" if [ "${format}" == "protobuf" ]; then - python3 gen_pb_compose.py "${case}" "${format}" + python3 integration_tests/scripts/gen_pb_compose.py "${case}" "${format}" fi echo "--- set vm.max_map_count=2000000 for doris" @@ -82,7 +80,7 @@ max_map_count_original_value=$(sysctl -n vm.max_map_count) sudo sysctl -w vm.max_map_count=2000000 echo "--- run Demos" -python3 run_demos.py --case "${case}" --format "${format}" +python3 integration_tests/scripts/run_demos.py --case "${case}" --format "${format}" echo "--- run docker ps" docker ps @@ -90,10 +88,7 @@ docker ps echo "--- check if the ingestion is successful" # extract the type of upstream source,e.g. mysql,postgres,etc upstream=$(echo "${case}" | cut -d'-' -f 1) -python3 check_data.py "${case}" "${upstream}" - -echo "--- clean Demos" -python3 clean_demos.py --case "${case}" +python3 integration_tests/scripts/check_data.py "${case}" "${upstream}" echo "--- reset vm.max_map_count={$max_map_count_original_value}" sudo sysctl -w vm.max_map_count="$max_map_count_original_value" diff --git a/ci/scripts/multi-arch-docker.sh b/ci/scripts/multi-arch-docker.sh index b97b606201d6a..012378347ba28 100755 --- a/ci/scripts/multi-arch-docker.sh +++ b/ci/scripts/multi-arch-docker.sh @@ -57,8 +57,20 @@ echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GHCR_USERNAME" --password-stdin echo "--- dockerhub login" echo "$DOCKER_TOKEN" | docker login -u "risingwavelabs" --password-stdin +if [[ -n "${ORIGINAL_IMAGE_TAG+x}" ]] && [[ -n "${NEW_IMAGE_TAG+x}" ]]; then + echo "--- retag docker image" + echo "push to gchr, image tag: ${ghcraddr}:${NEW_IMAGE_TAG}" + args=() + for arch in "${arches[@]}" + do + args+=( --amend "${ghcraddr}:${NEW_IMAGE_TAG}-${arch}" ) + done + docker manifest create --insecure "${ghcraddr}:${NEW_IMAGE_TAG}" "${args[@]}" + docker manifest push --insecure "${ghcraddr}:${NEW_IMAGE_TAG}" + exit 0 +fi -echo "--- multi arch image create " +echo "--- multi arch image create" if [[ "${#BUILDKITE_COMMIT}" = 40 ]]; then # If the commit is 40 characters long, it's probably a SHA. TAG="git-${BUILDKITE_COMMIT}" diff --git a/ci/scripts/release.sh b/ci/scripts/release.sh index 1c68b8acf9fa6..94fd38e2c9c75 100755 --- a/ci/scripts/release.sh +++ b/ci/scripts/release.sh @@ -19,11 +19,11 @@ echo "--- Install lld" dnf install -y lld ld.lld --version -echo "--- Install dependencies for openssl" -dnf install -y perl-core +echo "--- Install dependencies" +dnf install -y perl-core wget python3 python3-devel cyrus-sasl-devel rsync openssl-devel echo "--- Install java and maven" -dnf install -y java-11-openjdk java-11-openjdk-devel wget python3 python3-devel cyrus-sasl-devel +dnf install -y java-17-openjdk java-17-openjdk-devel pip3 install toml-cli wget https://rw-ci-deps-dist.s3.amazonaws.com/apache-maven-3.9.3-bin.tar.gz && tar -zxvf apache-maven-3.9.3-bin.tar.gz export PATH="${REPO_ROOT}/apache-maven-3.9.3/bin:$PATH" @@ -71,8 +71,8 @@ if [ "${ARCH}" == "aarch64" ]; then # see https://github.com/tikv/jemallocator/blob/802969384ae0c581255f3375ee2ba774c8d2a754/jemalloc-sys/build.rs#L218 export JEMALLOC_SYS_WITH_LG_PAGE=16 fi -cargo build -p risingwave_cmd_all --features "rw-static-link" --profile release -cargo build -p risingwave_cmd --bin risectl --features "rw-static-link" --profile release +OPENSSL_STATIC=1 cargo build -p risingwave_cmd_all --features "rw-static-link" --features external-udf --features wasm-udf --features js-udf --profile release +OPENSSL_STATIC=1 cargo build -p risingwave_cmd --bin risectl --features "rw-static-link" --profile release cd target/release && chmod +x risingwave risectl echo "--- Upload nightly binary to s3" diff --git a/ci/scripts/run-backfill-tests.sh b/ci/scripts/run-backfill-tests.sh index f01e2c5b3d771..c03762a7150a7 100755 --- a/ci/scripts/run-backfill-tests.sh +++ b/ci/scripts/run-backfill-tests.sh @@ -13,6 +13,7 @@ # 1002 | CREATE MATERIALIZED VIEW m1 AS SELECT * FROM t | 56.12% | 2023-09-27 06:37:06.636+00:00 #(1 row) +# TODO: refactor with inline style. set -euo pipefail @@ -22,7 +23,7 @@ TEST_DIR=$PWD/e2e_test BACKGROUND_DDL_DIR=$TEST_DIR/background_ddl COMMON_DIR=$BACKGROUND_DDL_DIR/common -CLUSTER_PROFILE='ci-1cn-1fe-kafka-with-recovery' +CLUSTER_PROFILE='ci-1cn-1fe-user-kafka-with-recovery' echo "--- Configuring cluster profiles" if [[ -n "${BUILDKITE:-}" ]]; then echo "Running in buildkite" @@ -184,16 +185,19 @@ test_sink_backfill_recovery() { # Check progress sqllogictest -p 4566 -d dev 'e2e_test/backfill/sink/create_sink.slt' + # Sleep before restart cluster, to ensure the downstream sink actually gets created. + sleep 5 + # Restart restart_cluster - sleep 3 + sleep 5 # Sink back into rw run_sql "CREATE TABLE table_kafka (v1 int primary key) WITH ( connector = 'kafka', topic = 's_kafka', - properties.bootstrap.server = 'localhost:29092', + properties.bootstrap.server = 'message_queue:29092', ) FORMAT DEBEZIUM ENCODE JSON;" sleep 10 diff --git a/ci/scripts/run-e2e-test.sh b/ci/scripts/run-e2e-test.sh index 5ce0b55f27e9e..4736f4aa53a8a 100755 --- a/ci/scripts/run-e2e-test.sh +++ b/ci/scripts/run-e2e-test.sh @@ -70,7 +70,7 @@ download-and-decompress-artifact e2e_test_generated ./ download-and-decompress-artifact risingwave_e2e_extended_mode_test-"$profile" target/debug/ mkdir -p e2e_test/udf/wasm/target/wasm32-wasi/release/ buildkite-agent artifact download udf.wasm e2e_test/udf/wasm/target/wasm32-wasi/release/ -buildkite-agent artifact download risingwave-udf-example.jar ./ +buildkite-agent artifact download udf.jar ./ mv target/debug/risingwave_e2e_extended_mode_test-"$profile" target/debug/risingwave_e2e_extended_mode_test chmod +x ./target/debug/risingwave_e2e_extended_mode_test @@ -80,6 +80,7 @@ RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=i cluster_start # Please make sure the regression is expected before increasing the timeout. sqllogictest -p 4566 -d dev './e2e_test/streaming/**/*.slt' --junit "streaming-${profile}" +sqllogictest -p 4566 -d dev './e2e_test/backfill/sink/different_pk_and_dist_key.slt' echo "--- Kill cluster" cluster_stop @@ -91,7 +92,11 @@ sqllogictest -p 4566 -d dev './e2e_test/ddl/**/*.slt' --junit "batch-ddl-${profi if [[ "$mode" != "single-node" ]]; then sqllogictest -p 4566 -d dev './e2e_test/background_ddl/basic.slt' --junit "batch-ddl-${profile}" fi -sqllogictest -p 4566 -d dev './e2e_test/visibility_mode/*.slt' --junit "batch-${profile}" + +if [[ $mode != "single-node" ]]; then + sqllogictest -p 4566 -d dev './e2e_test/visibility_mode/*.slt' --junit "batch-${profile}" +fi + sqllogictest -p 4566 -d dev './e2e_test/ttl/ttl.slt' sqllogictest -p 4566 -d dev './e2e_test/database/prepare.slt' sqllogictest -p 4566 -d test './e2e_test/database/test.slt' @@ -105,6 +110,7 @@ echo "--- e2e, $mode, Apache Superset" sqllogictest -p 4566 -d dev './e2e_test/superset/*.slt' --junit "batch-${profile}" echo "--- e2e, $mode, external python udf" +python3 -m pip install --break-system-packages arrow-udf==0.2.1 python3 e2e_test/udf/test.py & sleep 1 sqllogictest -p 4566 -d dev './e2e_test/udf/external_udf.slt' @@ -112,12 +118,11 @@ pkill python3 sqllogictest -p 4566 -d dev './e2e_test/udf/alter_function.slt' sqllogictest -p 4566 -d dev './e2e_test/udf/graceful_shutdown_python.slt' -sqllogictest -p 4566 -d dev './e2e_test/udf/always_retry_python.slt' # FIXME: flaky test # sqllogictest -p 4566 -d dev './e2e_test/udf/retry_python.slt' echo "--- e2e, $mode, external java udf" -java -jar risingwave-udf-example.jar & +java --add-opens=java.base/java.nio=org.apache.arrow.memory.core,ALL-UNNAMED -jar udf.jar & sleep 1 sqllogictest -p 4566 -d dev './e2e_test/udf/external_udf.slt' pkill java @@ -284,3 +289,6 @@ if [[ "$mode" == "standalone" ]]; then # Make sure any remaining background task exits. wait fi + +echo "--- Upload JUnit test results" +buildkite-agent artifact upload "*-junit.xml" diff --git a/ci/scripts/run-meta-backup-test.sh b/ci/scripts/run-meta-backup-test.sh index 120d247cedd47..098e4dfcd286c 100755 --- a/ci/scripts/run-meta-backup-test.sh +++ b/ci/scripts/run-meta-backup-test.sh @@ -67,14 +67,19 @@ cluster_stop() { download_and_prepare_rw "$profile" common -echo "--- e2e, ci-meta-backup-test" -test_root="src/storage/backup/integration_tests" -BACKUP_TEST_MCLI=".risingwave/bin/mcli" \ -BACKUP_TEST_MCLI_CONFIG=".risingwave/config/mcli" \ -BACKUP_TEST_RW_ALL_IN_ONE="target/debug/risingwave" \ -RW_HUMMOCK_URL="hummock+minio://hummockadmin:hummockadmin@127.0.0.1:9301/hummock001" \ -RW_META_ADDR="http://127.0.0.1:5690" \ -RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ -bash "${test_root}/run_all.sh" -echo "--- Kill cluster" -risedev kill \ No newline at end of file +sudo apt install sqlite3 -y +for meta_store_type in "etcd" "sql"; do + echo "--- e2e, ci-meta-backup-test-${meta_store_type}" + test_root="src/storage/backup/integration_tests" + BACKUP_TEST_MCLI=".risingwave/bin/mcli" \ + BACKUP_TEST_MCLI_CONFIG=".risingwave/config/mcli" \ + BACKUP_TEST_RW_ALL_IN_ONE="target/debug/risingwave" \ + RW_HUMMOCK_URL="hummock+minio://hummockadmin:hummockadmin@127.0.0.1:9301/hummock001" \ + RW_META_ADDR="http://127.0.0.1:5690" \ + RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ + META_STORE_TYPE="${meta_store_type}" \ + RW_SQLITE_DB=".risingwave/data/sqlite/metadata.db" \ + bash "${test_root}/run_all.sh" + echo "--- Kill cluster" + risedev kill +done diff --git a/ci/scripts/run-unit-test.sh b/ci/scripts/run-unit-test.sh index d9a723a34fa19..394cdb1a78261 100755 --- a/ci/scripts/run-unit-test.sh +++ b/ci/scripts/run-unit-test.sh @@ -5,11 +5,6 @@ set -euo pipefail REPO_ROOT=${PWD} -echo "+++ Run python UDF SDK unit tests" -cd "${REPO_ROOT}"/src/expr/udf/python -python3 -m pytest -cd "${REPO_ROOT}" - echo "+++ Run unit tests" # use tee to disable progress bar NEXTEST_PROFILE=ci cargo nextest run --features failpoints,sync_point --workspace --exclude risingwave_simulation diff --git a/ci/scripts/slow-e2e-test.sh b/ci/scripts/slow-e2e-test.sh new file mode 100755 index 0000000000000..dc33c2719053b --- /dev/null +++ b/ci/scripts/slow-e2e-test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +# Exits as soon as any line fails. +set -euo pipefail + +while getopts 'p:m:' opt; do + case ${opt} in + p ) + profile=$OPTARG + ;; + m ) + mode=$OPTARG + ;; + \? ) + echo "Invalid Option: -$OPTARG" 1>&2 + exit 1 + ;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + ;; + esac +done +shift $((OPTIND -1)) + +source ci/scripts/common.sh + +download_and_prepare_rw "$profile" common + + +echo "--- Download artifacts" +mkdir -p e2e_test/udf/wasm/target/wasm32-wasi/release/ +buildkite-agent artifact download udf.wasm e2e_test/udf/wasm/target/wasm32-wasi/release/ +buildkite-agent artifact download udf.jar ./ + +echo "--- e2e, $mode, slow-udf-tests" +python3 -m pip install --break-system-packages arrow-udf==0.2.1 +RUST_LOG="info" \ +risedev ci-start "$mode" +sqllogictest -p 4566 -d dev './e2e_test/slow_tests/udf/always_retry_python.slt' +sqllogictest -p 4566 -d dev './e2e_test/slow_tests/backfill/rate_limit/slow-udf.slt' diff --git a/ci/workflows/main-cron.yml b/ci/workflows/main-cron.yml index 0166b25797ab2..0f00c9e9c3ed2 100644 --- a/ci/workflows/main-cron.yml +++ b/ci/workflows/main-cron.yml @@ -7,10 +7,6 @@ auto-retry: &auto-retry steps: - label: "build" command: "ci/scripts/build.sh -p ci-release" - if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-build" - || build.env("CI_STEPS") =~ /(^|,)build(,|$$)/ key: "build" plugins: - docker-compose#v5.1.0: @@ -22,10 +18,6 @@ steps: - label: "build other components" command: "ci/scripts/build-other.sh" - if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-build-other" - || build.env("CI_STEPS") =~ /(^|,)build-other(,|$$)/ key: "build-other" plugins: - seek-oss/aws-sm#v2.3.1: @@ -42,10 +34,6 @@ steps: - label: "build simulation test" command: "ci/scripts/build-simulation.sh" - if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-build-simulation" - || build.env("CI_STEPS") =~ /(^|,)build-simulation(,|$$)/ key: "build-simulation" plugins: - docker-compose#v5.1.0: @@ -57,10 +45,6 @@ steps: - label: "docslt" command: "ci/scripts/docslt.sh" - if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-docslt" - || build.env("CI_STEPS") =~ /(^|,)docslt(,|$$)/ key: "docslt" plugins: - docker-compose#v5.1.0: @@ -74,7 +58,7 @@ steps: key: "e2e-test-release" command: "ci/scripts/cron-e2e-test.sh -p ci-release -m ci-3streaming-2serving-3fe" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-test" || build.env("CI_STEPS") =~ /(^|,)e2e-tests?(,|$$)/ depends_on: @@ -90,11 +74,30 @@ steps: timeout_in_minutes: 15 retry: *auto-retry + - label: "slow end-to-end test (release)" + key: "slow-e2e-test-release" + command: "ci/scripts/slow-e2e-test.sh -p ci-release -m ci-3streaming-2serving-3fe" + if: | + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null + || build.pull_request.labels includes "ci/run-slow-e2e-tests" + || build.env("CI_STEPS") =~ /(^|,)slow-e2e-tests?(,|$$)/ + depends_on: + - "build" + - "build-other" + plugins: + - docker-compose#v5.1.0: + run: rw-build-env + config: ci/docker-compose.yml + mount-buildkite-agent: true + - ./ci/plugins/upload-failure-logs + timeout_in_minutes: 8 + retry: *auto-retry + - label: "meta backup test (release)" key: "e2e-meta-backup-test-release" command: "ci/scripts/run-meta-backup-test.sh -p ci-release -m ci-3streaming-2serving-3fe" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-meta-backup-test" || build.env("CI_STEPS") =~ /(^|,)e2e-tests?(,|$$)/ depends_on: @@ -114,7 +117,7 @@ steps: key: "e2e-test-release-parallel" command: "ci/scripts/e2e-test-parallel.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-parallel-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-parallel-tests?(,|$$)/ depends_on: @@ -132,14 +135,14 @@ steps: files: "*-junit.xml" format: "junit" - ./ci/plugins/upload-failure-logs - timeout_in_minutes: 10 + timeout_in_minutes: 11 retry: *auto-retry - label: "end-to-end test (parallel, in-memory) (release)" key: "e2e-test-release-parallel-memory" command: "ci/scripts/e2e-test-parallel-in-memory.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-parallel-in-memory-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-parallel-in-memory-tests?(,|$$)/ depends_on: @@ -158,7 +161,7 @@ steps: key: "e2e-test-release-source" command: "ci/scripts/e2e-source-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-source-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-source-tests?(,|$$)/ depends_on: @@ -177,7 +180,7 @@ steps: key: "e2e-test-release-sink" command: "ci/scripts/e2e-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-sink-tests?(,|$$)/ depends_on: @@ -196,7 +199,7 @@ steps: key: "fuzz-test" command: "ci/scripts/cron-fuzz-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-sqlsmith-fuzzing-tests" || build.env("CI_STEPS") =~ /(^|,)sqlsmith-fuzzing-tests?(,|$$)/ depends_on: @@ -218,7 +221,7 @@ steps: key: "unit-test" command: "ci/scripts/unit-test.sh" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-unit-test" || build.env("CI_STEPS") =~ /(^|,)unit-tests?(,|$$)/ plugins: @@ -236,9 +239,9 @@ steps: - label: "unit test (madsim)" key: "unit-test-deterministic" - command: "MADSIM_TEST_NUM=100 timeout 15m ci/scripts/deterministic-unit-test.sh" + command: "MADSIM_TEST_NUM=100 timeout 30m ci/scripts/deterministic-unit-test.sh" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-unit-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)unit-tests?-deterministic-simulation(,|$$)/ plugins: @@ -246,14 +249,14 @@ steps: run: rw-build-env config: ci/docker-compose.yml mount-buildkite-agent: true - timeout_in_minutes: 15 + timeout_in_minutes: 30 retry: *auto-retry - label: "integration test (madsim) - scale" key: "integration-test-deterministic-scale" command: "TEST_NUM=60 ci/scripts/deterministic-it-test.sh scale::" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-integration-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)integration-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -270,7 +273,7 @@ steps: key: "integration-test-deterministic-recovery" command: "TEST_NUM=60 ci/scripts/deterministic-it-test.sh recovery::" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-integration-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)integration-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -287,7 +290,7 @@ steps: key: "integration-test-deterministic-backfill" command: "TEST_NUM=30 ci/scripts/deterministic-it-test.sh backfill_tests::" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-integration-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)integration-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -304,7 +307,7 @@ steps: key: "integration-test-deterministic-storage" command: "TEST_NUM=30 ci/scripts/deterministic-it-test.sh storage::" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-integration-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)integration-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -321,7 +324,7 @@ steps: key: "integration-test-deterministic-sink" command: "TEST_NUM=30 ci/scripts/deterministic-it-test.sh sink::" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-integration-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)integration-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -338,7 +341,7 @@ steps: key: "e2e-test-deterministic" command: "TEST_NUM=64 timeout 75m ci/scripts/deterministic-e2e-test.sh" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)e2e-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -360,7 +363,7 @@ steps: key: "recovery-test-deterministic" command: "TEST_NUM=12 KILL_RATE=1.0 BACKGROUND_DDL_RATE=0.0 timeout 65m ci/scripts/deterministic-recovery-test.sh" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-recovery-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)recovery-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -379,26 +382,7 @@ steps: key: "background-ddl-arrangement-backfill-recovery-test-deterministic" command: "TEST_NUM=12 KILL_RATE=1.0 BACKGROUND_DDL_RATE=0.8 USE_ARRANGEMENT_BACKFILL=--use-arrangement-backfill timeout 65m ci/scripts/deterministic-recovery-test.sh" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-recovery-test-deterministic-simulation" - || build.env("CI_STEPS") =~ /(^|,)recovery-tests?-deterministic-simulation(,|$$)/ - depends_on: "build-simulation" - plugins: - - docker-compose#v5.1.0: - run: rw-build-env - config: ci/docker-compose.yml - mount-buildkite-agent: true - # Only upload zipped files, otherwise the logs is too much. - - ./ci/plugins/upload-failure-logs-zipped - timeout_in_minutes: 70 - retry: *auto-retry - - # Ddl statements will randomly run with background_ddl. - - label: "background_ddl recovery test (madsim)" - key: "background-ddl-recovery-test-deterministic" - command: "TEST_NUM=12 KILL_RATE=1.0 BACKGROUND_DDL_RATE=0.8 timeout 65m ci/scripts/deterministic-recovery-test.sh" - if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-recovery-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)recovery-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -416,7 +400,7 @@ steps: key: "e2e-iceberg-sink-test" command: "ci/scripts/e2e-iceberg-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-iceberg-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-iceberg-sink-tests?(,|$$)/ depends_on: @@ -436,7 +420,7 @@ steps: key: "e2e-iceberg-sink-v2-test" command: "ci/scripts/e2e-iceberg-sink-v2-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-iceberg-sink-v2-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-iceberg-sink-v2-tests?(,|$$)/ depends_on: @@ -455,7 +439,7 @@ steps: key: "e2e-java-binding-tests" command: "ci/scripts/java-binding-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-java-binding-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-java-binding-tests?(,|$$)/ depends_on: @@ -476,7 +460,7 @@ steps: key: "s3-v2-source-check-aws-json-parser" command: "ci/scripts/s3-source-test.sh -p ci-release -s fs_source_v2.py -t json" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-s3-source-tests" || build.env("CI_STEPS") =~ /(^|,)s3-source-tests?(,|$$)/ depends_on: build @@ -520,7 +504,7 @@ steps: key: "s3-v2-source-batch-read-check-aws-json-parser" command: "ci/scripts/s3-source-test.sh -p ci-release -s fs_source_batch.py -t json" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-s3-source-tests" || build.env("CI_STEPS") =~ /(^|,)s3-source-tests?(,|$$)/ depends_on: build @@ -542,7 +526,7 @@ steps: key: "s3-v2-source-check-aws-csv-parser" command: "ci/scripts/s3-source-test.sh -p ci-release -s fs_source_v2.py -t csv_without_header" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-s3-source-tests" || build.env("CI_STEPS") =~ /(^|,)s3-source-tests?(,|$$)/ depends_on: build @@ -564,7 +548,7 @@ steps: key: "s3-source-test-for-opendal-fs-engine-csv-parser" command: "ci/scripts/s3-source-test.sh -p ci-release -s posix_fs_source.py -t csv_without_header" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-s3-source-tests" || build.env("CI_STEPS") =~ /(^|,)s3-source-tests?(,|$$)/ depends_on: build @@ -582,7 +566,7 @@ steps: # key: "s3-source-test-for-opendal-fs-engine" # command: "ci/scripts/s3-source-test-for-opendal-fs-engine.sh -p ci-release -s gcs_source.py" # if: | - # !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + # !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null # || build.pull_request.labels includes "ci/run-s3-source-tests" # || build.env("CI_STEPS") =~ /(^|,)s3-source-tests?(,|$$)/ # depends_on: build @@ -604,7 +588,7 @@ steps: key: "pulsar-source-tests" command: "ci/scripts/pulsar-source-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-pulsar-source-tests" || build.env("CI_STEPS") =~ /(^|,)pulsar-source-tests?(,|$$)/ depends_on: @@ -629,7 +613,7 @@ steps: key: "run-micro-benchmarks" command: "ci/scripts/run-micro-benchmarks.sh" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-micro-benchmarks" || build.env("CI_STEPS") =~ /(^|,)micro-benchmarks?(,|$$)/ plugins: @@ -644,7 +628,7 @@ steps: key: "upload-micro-benchmarks" if: | build.branch == "main" - || !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + || !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-micro-benchmarks" || build.env("CI_STEPS") =~ /(^|,)micro-benchmarks?(,|$$)/ command: @@ -669,14 +653,14 @@ steps: key: "backwards-compat-tests" command: "VERSION_OFFSET={{matrix.version_offset}} RW_COMMIT=$BUILDKITE_COMMIT ci/scripts/backwards-compat-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-backwards-compat-tests" || build.env("CI_STEPS") =~ /(^|,)backwards?-compat-tests?(,|$$)/ depends_on: - "build" plugins: - docker-compose#v5.1.0: - run: rw-build-env + run: source-test-env config: ci/docker-compose.yml mount-buildkite-agent: true - ./ci/plugins/upload-failure-logs @@ -703,7 +687,7 @@ steps: key: "sqlsmith-differential-tests" command: "ci/scripts/sqlsmith-differential-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-sqlsmith-differential-tests" || build.env("CI_STEPS") =~ /(^|,)sqlsmith-differential-tests?(,|$$)/ depends_on: @@ -719,14 +703,14 @@ steps: key: "backfill-tests" command: "BUILDKITE=${BUILDKITE:-} ci/scripts/backfill-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-backfill-tests" || build.env("CI_STEPS") =~ /(^|,)backfill-tests?(,|$$)/ depends_on: - "build" plugins: - docker-compose#v5.1.0: - run: rw-build-env + run: source-test-env config: ci/docker-compose.yml mount-buildkite-agent: true - ./ci/plugins/upload-failure-logs @@ -737,7 +721,7 @@ steps: key: "e2e-standalone-binary-tests" command: "ci/scripts/e2e-test.sh -p ci-release -m standalone" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-standalone-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-standalone-tests?(,|$$)/ depends_on: @@ -757,7 +741,7 @@ steps: key: "e2e-single-node-binary-tests" command: "ci/scripts/e2e-test.sh -p ci-release -m single-node" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-single-node-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-single-node-tests?(,|$$)/ depends_on: @@ -777,7 +761,7 @@ steps: key: "e2e-test-opendal-parallel" command: "ci/scripts/e2e-test-parallel-for-opendal.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-parallel-tests-for-opendal" || build.env("CI_STEPS") =~ /(^|,)e2e-parallel-tests?-for-opendal(,|$$)/ depends_on: @@ -796,7 +780,7 @@ steps: key: "e2e-deltalake-sink-rust-tests" command: "ci/scripts/e2e-deltalake-sink-rust-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-deltalake-sink-rust-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-deltalake-sink-rust-tests?(,|$$)/ depends_on: @@ -815,7 +799,7 @@ steps: key: "e2e-redis-sink-tests" command: "ci/scripts/e2e-redis-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-redis-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-redis-sink-tests?(,|$$)/ depends_on: @@ -834,7 +818,7 @@ steps: key: "e2e-doris-sink-tests" command: "ci/scripts/e2e-doris-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-doris-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-doris-sink-tests?(,|$$)/ depends_on: @@ -853,7 +837,7 @@ steps: key: "e2e-starrocks-sink-tests" command: "ci/scripts/e2e-starrocks-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-starrocks-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-starrocks-sink-tests?(,|$$)/ depends_on: @@ -872,7 +856,7 @@ steps: key: "e2e-cassandra-sink-tests" command: "ci/scripts/e2e-cassandra-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-cassandra-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-cassandra-sink-tests?(,|$$)/ depends_on: @@ -891,7 +875,7 @@ steps: key: "e2e-clickhouse-sink-tests" command: "ci/scripts/e2e-clickhouse-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-clickhouse-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-clickhouse-sink-tests?(,|$$)/ depends_on: @@ -906,11 +890,30 @@ steps: timeout_in_minutes: 10 retry: *auto-retry + - label: "end-to-end sqlserver sink test" + key: "e2e-sqlserver-sink-tests" + command: "ci/scripts/e2e-sqlserver-sink-test.sh -p ci-release" + if: | + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null + || build.pull_request.labels includes "ci/run-e2e-sqlserver-sink-tests" + || build.env("CI_STEPS") =~ /(^|,)e2e-sqlserver-sink-tests?(,|$$)/ + depends_on: + - "build" + - "build-other" + plugins: + - docker-compose#v5.1.0: + run: sink-test-env + config: ci/docker-compose.yml + mount-buildkite-agent: true + - ./ci/plugins/upload-failure-logs + timeout_in_minutes: 10 + retry: *auto-retry + - label: "end-to-end pulsar sink test" key: "e2e-pulsar-sink-tests" command: "ci/scripts/e2e-pulsar-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-pulsar-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-pulsar-sink-tests?(,|$$)/ depends_on: @@ -929,7 +932,7 @@ steps: key: "e2e-mqtt-sink-tests" command: "ci/scripts/e2e-mqtt-sink-test.sh -p ci-release" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-mqtt-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-mqtt-sink-tests?(,|$$)/ depends_on: @@ -944,11 +947,30 @@ steps: timeout_in_minutes: 10 retry: *auto-retry + - label: "end-to-end mongodb sink test" + key: "e2e-mongodb-sink-tests" + command: "ci/scripts/e2e-mongodb-sink-test.sh -p ci-release" + if: | + !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + || build.pull_request.labels includes "ci/run-e2e-mongodb-sink-tests" + || build.env("CI_STEPS") =~ /(^|,)e2e-mongodb-sink-tests?(,|$$)/ + depends_on: + - "build" + - "build-other" + plugins: + - docker-compose#v5.1.0: + run: sink-test-env + config: ci/docker-compose.yml + mount-buildkite-agent: true + - ./ci/plugins/upload-failure-logs + timeout_in_minutes: 10 + retry: *auto-retry + - label: "connector node integration test Java {{matrix.java_version}}" key: "connector-node-integration-test" command: "ci/scripts/connector-node-integration-test.sh -p ci-release -v {{matrix.java_version}}" if: | - !(build.pull_request.labels includes "ci/main-cron/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/main-cron/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-connector-node-integration-tests" || build.env("CI_STEPS") =~ /(^|,)connector-node-integration-tests?(,|$$)/ depends_on: diff --git a/ci/workflows/pull-request.yml b/ci/workflows/pull-request.yml index c19b9a774fc12..ae8db23bbeb81 100644 --- a/ci/workflows/pull-request.yml +++ b/ci/workflows/pull-request.yml @@ -1,10 +1,24 @@ auto-retry: &auto-retry - automatic: # Agent terminated because the AWS EC2 spot instance killed by AWS. - signal_reason: agent_stop limit: 3 +cargo-cache: &cargo-cache + id: cargo + key: "v1-cache-{{ id }}-{{ runner.os }}-{{ checksum 'Cargo.lock' }}" + restore-keys: + - "v1-cache-{{ id }}-{{ runner.os }}-" + - "v1-cache-{{ id }}-" + backend: s3 + s3: + bucket: rw-ci-cache-bucket + args: '--no-progress' + paths: + - ".cargo/registry/index" + - ".cargo/registry/cache" + - ".cargo/git" + steps: - label: "check ci image rebuild" plugins: @@ -20,11 +34,8 @@ steps: - label: "build" command: "ci/scripts/build.sh -p ci-dev" key: "build" - if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-build" - || build.env("CI_STEPS") =~ /(^|,)build(,|$$)/ plugins: + - gencer/cache#v2.4.10: *cargo-cache - docker-compose#v5.1.0: run: rw-build-env config: ci/docker-compose.yml @@ -35,11 +46,8 @@ steps: - label: "build other components" command: "ci/scripts/build-other.sh" key: "build-other" - if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-build-other" - || build.env("CI_STEPS") =~ /(^|,)build-other(,|$$)/ plugins: + - gencer/cache#v2.4.10: *cargo-cache - seek-oss/aws-sm#v2.3.1: env: GITHUB_TOKEN: github-token @@ -55,11 +63,8 @@ steps: - label: "build (deterministic simulation)" command: "ci/scripts/build-simulation.sh" key: "build-simulation" - if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-build-simulation" - || build.env("CI_STEPS") =~ /(^|,)build-simulation(,|$$)/ plugins: + - gencer/cache#v2.4.10: *cargo-cache - docker-compose#v5.1.0: run: rw-build-env config: ci/docker-compose.yml @@ -69,11 +74,8 @@ steps: - label: "docslt" command: "ci/scripts/docslt.sh" key: "docslt" - if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null - || build.pull_request.labels includes "ci/run-docslt" - || build.env("CI_STEPS") =~ /(^|,)docslt(,|$$)/ plugins: + - gencer/cache#v2.4.10: *cargo-cache - docker-compose#v5.1.0: run: rw-build-env config: ci/docker-compose.yml @@ -84,7 +86,7 @@ steps: - label: "end-to-end test" command: "ci/scripts/e2e-test.sh -p ci-dev -m ci-3streaming-2serving-3fe" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-test" || build.env("CI_STEPS") =~ /(^|,)e2e-tests?(,|$$)/ depends_on: @@ -97,13 +99,50 @@ steps: config: ci/docker-compose.yml mount-buildkite-agent: true - ./ci/plugins/upload-failure-logs - timeout_in_minutes: 21 + timeout_in_minutes: 23 + retry: *auto-retry + + - label: "slow end-to-end test" + key: "slow-e2e-test" + command: "ci/scripts/slow-e2e-test.sh -p ci-dev -m ci-3streaming-2serving-3fe" + if: | + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null + || build.pull_request.labels includes "ci/run-slow-e2e-tests" + || build.env("CI_STEPS") =~ /(^|,)slow-e2e-tests?(,|$$)/ + depends_on: + - "build" + - "build-other" + plugins: + - docker-compose#v5.1.0: + run: rw-build-env + config: ci/docker-compose.yml + mount-buildkite-agent: true + - ./ci/plugins/upload-failure-logs + timeout_in_minutes: 8 + retry: *auto-retry + + - label: "meta backup test" + key: "e2e-meta-backup-test" + command: "ci/scripts/run-meta-backup-test.sh -p ci-dev -m ci-3streaming-2serving-3fe" + if: | + build.pull_request.labels includes "ci/run-e2e-meta-backup-test" + depends_on: + - "build" + - "build-other" + - "docslt" + plugins: + - docker-compose#v5.1.0: + run: rw-build-env + config: ci/docker-compose.yml + mount-buildkite-agent: true + - ./ci/plugins/upload-failure-logs + timeout_in_minutes: 45 retry: *auto-retry - label: "end-to-end test (parallel)" command: "ci/scripts/e2e-test-parallel.sh -p ci-dev" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-parallel-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-parallel-tests?(,|$$)/ depends_on: @@ -115,7 +154,7 @@ steps: config: ci/docker-compose.yml mount-buildkite-agent: true - ./ci/plugins/upload-failure-logs - timeout_in_minutes: 15 + timeout_in_minutes: 17 retry: *auto-retry - label: "end-to-end test for opendal (parallel)" @@ -149,7 +188,7 @@ steps: - label: "end-to-end source test" command: "ci/scripts/e2e-source-test.sh -p ci-dev" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-source-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-source-tests?(,|$$)/ depends_on: @@ -160,6 +199,7 @@ steps: run: source-test-env config: ci/docker-compose.yml mount-buildkite-agent: true + upload-container-logs: always - ./ci/plugins/upload-failure-logs timeout_in_minutes: 18 retry: *auto-retry @@ -167,7 +207,7 @@ steps: - label: "end-to-end sink test" command: "ci/scripts/e2e-sink-test.sh -p ci-dev" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-sink-tests?(,|$$)/ depends_on: @@ -293,6 +333,21 @@ steps: timeout_in_minutes: 10 retry: *auto-retry + - label: "end-to-end sqlserver sink test" + if: build.pull_request.labels includes "ci/run-e2e-sqlserver-sink-tests" || build.env("CI_STEPS") =~ /(^|,)e2e-sqlserver-sink-tests?(,|$$)/ + command: "ci/scripts/e2e-sqlserver-sink-test.sh -p ci-dev" + depends_on: + - "build" + - "build-other" + plugins: + - docker-compose#v5.1.0: + run: sink-test-env + config: ci/docker-compose.yml + mount-buildkite-agent: true + - ./ci/plugins/upload-failure-logs + timeout_in_minutes: 10 + retry: *auto-retry + - label: "end-to-end deltalake sink test" if: build.pull_request.labels includes "ci/run- e2e-deltalake-sink-rust-tests" || build.env("CI_STEPS") =~ /(^|,) e2e-deltalake-sink-rust-tests?(,|$$)/ command: "ci/scripts/e2e-deltalake-sink-rust-test.sh -p ci-dev" @@ -368,6 +423,21 @@ steps: timeout_in_minutes: 10 retry: *auto-retry + - label: "end-to-end mongodb sink test" + if: build.pull_request.labels includes "ci/run-e2e-mongodb-sink-tests" || build.env("CI_STEPS") =~ /(^|,) e2e-mongodb-sink-tests?(,|$$)/ + command: "ci/scripts/e2e-mongodb-sink-test.sh -p ci-dev" + depends_on: + - "build" + - "build-other" + plugins: + - docker-compose#v5.1.0: + run: sink-test-env + config: ci/docker-compose.yml + mount-buildkite-agent: true + - ./ci/plugins/upload-failure-logs + timeout_in_minutes: 10 + retry: *auto-retry + - label: "e2e java-binding test" if: build.pull_request.labels includes "ci/run-java-binding-tests" || build.env("CI_STEPS") =~ /(^|,)java-binding-tests?(,|$$)/ command: "ci/scripts/java-binding-test.sh -p ci-dev" @@ -386,7 +456,7 @@ steps: - label: "regress test" command: "ci/scripts/regress-test.sh -p ci-dev" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-regress-test" || build.env("CI_STEPS") =~ /(^|,)regress-tests?(,|$$)/ depends_on: "build" @@ -405,10 +475,11 @@ steps: - label: "unit test" command: "ci/scripts/pr-unit-test.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-unit-test" || build.env("CI_STEPS") =~ /(^|,)unit-tests?(,|$$)/ plugins: + - gencer/cache#v2.4.10: *cargo-cache - ./ci/plugins/swapfile - seek-oss/aws-sm#v2.3.1: env: @@ -424,22 +495,11 @@ steps: - label: "check" command: "ci/scripts/check.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-check" || build.env("CI_STEPS") =~ /(^|,)check(,|$$)/ plugins: - - gencer/cache#v2.4.10: - id: cache - key: "v1-cache-{{ id }}-{{ runner.os }}-{{ checksum 'Cargo.lock' }}" - restore-keys: - - "v1-cache-{{ id }}-{{ runner.os }}-" - - "v1-cache-{{ id }}-" - backend: s3 - s3: - bucket: rw-ci-cache-bucket - args: "--no-progress" - paths: - - ".cargo/advisory-db" + - gencer/cache#v2.4.10: *cargo-cache - docker-compose#v5.1.0: run: rw-build-env config: ci/docker-compose.yml @@ -449,22 +509,11 @@ steps: - label: "check dylint" command: "ci/scripts/check-dylint.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-check" || build.env("CI_STEPS") =~ /(^|,)check(,|$$)/ plugins: - - gencer/cache#v2.4.10: - id: cache - key: "v1-cache-{{ id }}-{{ runner.os }}-{{ checksum 'Cargo.lock' }}" - restore-keys: - - "v1-cache-{{ id }}-{{ runner.os }}-" - - "v1-cache-{{ id }}-" - backend: s3 - s3: - bucket: rw-ci-cache-bucket - args: "--no-progress" - paths: - - ".cargo/advisory-db" + - gencer/cache#v2.4.10: *cargo-cache - docker-compose#v5.1.0: run: rw-build-env config: ci/docker-compose.yml @@ -474,7 +523,7 @@ steps: - label: "unit test (deterministic simulation)" command: "ci/scripts/deterministic-unit-test.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-unit-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)unit-tests?-deterministic-simulation(,|$$)/ plugins: @@ -489,7 +538,7 @@ steps: - label: "integration test (deterministic simulation)" command: "TEST_NUM=5 ci/scripts/deterministic-it-test.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-integration-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)integration-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -505,7 +554,7 @@ steps: - label: "end-to-end test (deterministic simulation)" command: "TEST_NUM=16 ci/scripts/deterministic-e2e-test.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-e2e-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)e2e-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -527,7 +576,7 @@ steps: - label: "recovery test (deterministic simulation)" command: "TEST_NUM=8 KILL_RATE=1.0 BACKGROUND_DDL_RATE=0.0 ci/scripts/deterministic-recovery-test.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-recovery-test-deterministic-simulation" || build.env("CI_STEPS") =~ /(^|,)recovery-tests?-deterministic-simulation(,|$$)/ depends_on: "build-simulation" @@ -551,7 +600,7 @@ steps: - label: "misc check" command: "ci/scripts/misc-check.sh" if: | - !(build.pull_request.labels includes "ci/skip-ci") && build.env("CI_STEPS") == null + !(build.pull_request.labels includes "ci/pr/run-selected") && build.env("CI_STEPS") == null || build.pull_request.labels includes "ci/run-misc-check" || build.env("CI_STEPS") =~ /(^|,)misc-check(,|$$)/ plugins: @@ -642,7 +691,7 @@ steps: - "build" plugins: - docker-compose#v5.1.0: - run: rw-build-env + run: source-test-env config: ci/docker-compose.yml mount-buildkite-agent: true - ./ci/plugins/upload-failure-logs @@ -683,7 +732,7 @@ steps: - "build" plugins: - docker-compose#v5.1.0: - run: rw-build-env + run: source-test-env config: ci/docker-compose.yml mount-buildkite-agent: true - ./ci/plugins/upload-failure-logs @@ -769,10 +818,10 @@ steps: timeout_in_minutes: 15 retry: *auto-retry - - label: "enable ci/skip-ci only in draft PRs" - if: build.pull_request.labels includes "ci/skip-ci" && !build.pull_request.draft + - label: "enable ci/pr/run-selected only in draft PRs" + if: build.pull_request.labels includes "ci/pr/run-selected" && !build.pull_request.draft commands: - - echo "ci/skip-ci is only usable for draft Pull Requests" + - echo "ci/pr/run-selected is only usable for draft Pull Requests" - exit 1 - label: "micro benchmark" diff --git a/dashboard/components/Layout.tsx b/dashboard/components/Layout.tsx index 7be9f727bddec..6daa7e821ce3c 100644 --- a/dashboard/components/Layout.tsx +++ b/dashboard/components/Layout.tsx @@ -140,6 +140,7 @@ function Layout({ children }: { children: React.ReactNode }) { Internal Tables Sinks Views + Subscriptions
Streaming diff --git a/dashboard/components/Relations.tsx b/dashboard/components/Relations.tsx index 6d04d041b368f..49161feea908b 100644 --- a/dashboard/components/Relations.tsx +++ b/dashboard/components/Relations.tsx @@ -33,7 +33,13 @@ import Link from "next/link" import { Fragment } from "react" import Title from "../components/Title" import useFetch from "../lib/api/fetch" -import { Relation, StreamingJob } from "../lib/api/streaming" +import { + Relation, + StreamingJob, + getDatabases, + getSchemas, + getUsers, +} from "../lib/api/streaming" import extractColumnInfo from "../lib/extractInfo" import { Sink as RwSink, @@ -114,7 +120,22 @@ export function Relations( getRelations: () => Promise, extraColumns: Column[] ) { - const { response: relationList } = useFetch(getRelations) + const { response: relationList } = useFetch(async () => { + const relations = await getRelations() + const users = await getUsers() + const databases = await getDatabases() + const schemas = await getSchemas() + return relations.map((r) => { + // Add owner, schema, and database names. It's linear search but the list is small. + const owner = users.find((u) => u.id === r.owner) + const ownerName = owner?.name + const schema = schemas.find((s) => s.id === r.schemaId) + const schemaName = schema?.name + const database = databases.find((d) => d.id === r.databaseId) + const databaseName = database?.name + return { ...r, ownerName, schemaName, databaseName } + }) + }) const [modalData, setModalId] = useCatalogModal(relationList) const modal = ( @@ -129,6 +150,8 @@ export function Relations( Id + Database + Schema Name Owner {extraColumns.map((c) => ( @@ -153,17 +176,23 @@ export function Relations( {r.id} + {r.databaseName} + {r.schemaName} {r.name} - {r.owner} + {r.ownerName} {extraColumns.map((c) => ( {c.content(r)} ))} - - {r.columns - .filter((col) => ("isHidden" in col ? !col.isHidden : true)) - .map((col) => extractColumnInfo(col)) - .join(", ")} - + {r.columns && r.columns.length > 0 && ( + + {r.columns + .filter((col) => + "isHidden" in col ? !col.isHidden : true + ) + .map((col) => extractColumnInfo(col)) + .join(", ")} + + )} ))} diff --git a/dashboard/lib/api/api.ts b/dashboard/lib/api/api.ts index 0df0454295482..af0116e31f3bb 100644 --- a/dashboard/lib/api/api.ts +++ b/dashboard/lib/api/api.ts @@ -26,7 +26,7 @@ export const PREDEFINED_API_ENDPOINTS = [ ] export const DEFAULT_API_ENDPOINT: string = - process.env.NODE_ENV === "production" ? PROD_API_ENDPOINT : MOCK_API_ENDPOINT + process.env.NODE_ENV === "production" ? PROD_API_ENDPOINT : MOCK_API_ENDPOINT // EXTERNAL_META_NODE_API_ENDPOINT to debug with RisingWave servers export const API_ENDPOINT_KEY = "risingwave.dashboard.api.endpoint" diff --git a/dashboard/lib/api/fetch.ts b/dashboard/lib/api/fetch.ts index 7aa34826e6a5b..6cf980202b548 100644 --- a/dashboard/lib/api/fetch.ts +++ b/dashboard/lib/api/fetch.ts @@ -33,6 +33,7 @@ export default function useFetch( const [response, setResponse] = useState() const toast = useErrorToast() + // NOTE(eric): Don't put `fetchFn` in the dependency array. It might be a lambda function useEffect(() => { const fetchData = async () => { if (when) { @@ -52,7 +53,7 @@ export default function useFetch( const timer = setInterval(fetchData, intervalMs) return () => clearInterval(timer) - }, [toast, fetchFn, intervalMs, when]) + }, [toast, intervalMs, when]) return { response } } diff --git a/dashboard/lib/api/streaming.ts b/dashboard/lib/api/streaming.ts index 4295e639fb218..948cd567d3f2b 100644 --- a/dashboard/lib/api/streaming.ts +++ b/dashboard/lib/api/streaming.ts @@ -17,12 +17,21 @@ import _ from "lodash" import sortBy from "lodash/sortBy" -import { Sink, Source, Table, View } from "../../proto/gen/catalog" +import { + Database, + Schema, + Sink, + Source, + Subscription, + Table, + View, +} from "../../proto/gen/catalog" import { ListObjectDependenciesResponse_ObjectDependencies as ObjectDependencies, TableFragments, } from "../../proto/gen/meta" import { ColumnCatalog, Field } from "../../proto/gen/plan_common" +import { UserInfo } from "../../proto/gen/user" import api from "./api" export async function getFragments(): Promise { @@ -37,7 +46,14 @@ export interface Relation { id: number name: string owner: number - columns: (ColumnCatalog | Field)[] + schemaId: number + databaseId: number + + // For display + columns?: (ColumnCatalog | Field)[] + ownerName?: string + schemaName?: string + databaseName?: string } export interface StreamingJob extends Relation { @@ -51,6 +67,8 @@ export function relationType(x: Relation) { return "SINK" } else if ((x as Source).info !== undefined) { return "SOURCE" + } else if ((x as Subscription).dependentTableId !== undefined) { + return "SUBSCRIPTION" } else { return "UNKNOWN" } @@ -83,7 +101,8 @@ export async function getRelations() { await getTables(), await getIndexes(), await getSinks(), - await getSources() + await getSources(), + await getSubscriptions() ) relations = sortBy(relations, (x) => x.id) return relations @@ -135,6 +154,34 @@ export async function getViews() { return views } +export async function getSubscriptions() { + let subscriptions: Subscription[] = (await api.get("/subscriptions")).map( + Subscription.fromJSON + ) + subscriptions = sortBy(subscriptions, (x) => x.id) + return subscriptions +} + +export async function getUsers() { + let users: UserInfo[] = (await api.get("/users")).map(UserInfo.fromJSON) + users = sortBy(users, (x) => x.id) + return users +} + +export async function getDatabases() { + let databases: Database[] = (await api.get("/databases")).map( + Database.fromJSON + ) + databases = sortBy(databases, (x) => x.id) + return databases +} + +export async function getSchemas() { + let schemas: Schema[] = (await api.get("/schemas")).map(Schema.fromJSON) + schemas = sortBy(schemas, (x) => x.id) + return schemas +} + export async function getObjectDependencies() { let objDependencies: ObjectDependencies[] = ( await api.get("/object_dependencies") diff --git a/dashboard/mock-server.js b/dashboard/mock-server.js index 2db52df788e22..50c55e12686b8 100644 --- a/dashboard/mock-server.js +++ b/dashboard/mock-server.js @@ -45,6 +45,10 @@ app.get("/indexes", (req, res, next) => { res.json(require("./mock/indexes.json")) }) +app.get("/indexes", (req, res, next) => { + res.json(require("./mock/indexes.json")) +}) + app.get("/internal_tables", (req, res, next) => { res.json(require("./mock/internal_tables.json")) }) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 1c462e1675207..c06e209600477 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -6,6 +6,7 @@ "": { "hasInstallScript": true, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", @@ -26,7 +27,7 @@ "fabric": "^5.2.1", "framer-motion": "^6.5.1", "lodash": "^4.17.21", - "next": "^14.1.0", + "next": "^14.1.1", "nuqs": "^1.14.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2092,9 +2093,9 @@ } }, "node_modules/@next/env": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", - "integrity": "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==" + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.1.tgz", + "integrity": "sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.1.0", @@ -2161,9 +2162,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz", - "integrity": "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz", + "integrity": "sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==", "cpu": [ "arm64" ], @@ -2176,9 +2177,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", - "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz", + "integrity": "sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==", "cpu": [ "x64" ], @@ -2191,9 +2192,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", - "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz", + "integrity": "sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==", "cpu": [ "arm64" ], @@ -2206,9 +2207,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", - "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz", + "integrity": "sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==", "cpu": [ "arm64" ], @@ -2221,9 +2222,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", - "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz", + "integrity": "sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==", "cpu": [ "x64" ], @@ -2236,9 +2237,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", - "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz", + "integrity": "sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==", "cpu": [ "x64" ], @@ -2251,9 +2252,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", - "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz", + "integrity": "sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==", "cpu": [ "arm64" ], @@ -2266,9 +2267,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", - "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz", + "integrity": "sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==", "cpu": [ "ia32" ], @@ -2281,9 +2282,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", - "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz", + "integrity": "sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==", "cpu": [ "x64" ], @@ -3350,6 +3351,14 @@ "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.1.0.tgz", "integrity": "sha512-PeaBcTmdZWcFf7n1aM+oiOdZc+sy14qi0emPIeUuGMTjbP0xLGrZu43kdpHnWSXy7/r4Ubp/vlg50MCV8+9Isg==" }, + "node_modules/16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "dependencies": { + "numeric": "^1.2.6" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -3861,12 +3870,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6316,9 +6325,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -8538,11 +8547,11 @@ } }, "node_modules/next": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", - "integrity": "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.1.tgz", + "integrity": "sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==", "dependencies": { - "@next/env": "14.1.0", + "@next/env": "14.1.1", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -8557,15 +8566,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.1.0", - "@next/swc-darwin-x64": "14.1.0", - "@next/swc-linux-arm64-gnu": "14.1.0", - "@next/swc-linux-arm64-musl": "14.1.0", - "@next/swc-linux-x64-gnu": "14.1.0", - "@next/swc-linux-x64-musl": "14.1.0", - "@next/swc-win32-arm64-msvc": "14.1.0", - "@next/swc-win32-ia32-msvc": "14.1.0", - "@next/swc-win32-x64-msvc": "14.1.0" + "@next/swc-darwin-arm64": "14.1.1", + "@next/swc-darwin-x64": "14.1.1", + "@next/swc-linux-arm64-gnu": "14.1.1", + "@next/swc-linux-arm64-musl": "14.1.1", + "@next/swc-linux-x64-gnu": "14.1.1", + "@next/swc-linux-x64-musl": "14.1.1", + "@next/swc-win32-arm64-msvc": "14.1.1", + "@next/swc-win32-ia32-msvc": "14.1.1", + "@next/swc-win32-x64-msvc": "14.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -8680,6 +8689,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "node_modules/nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", @@ -11499,16 +11513,16 @@ "devOptional": true }, "node_modules/ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "optional": true, "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -11586,6 +11600,14 @@ } }, "dependencies": { + "16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "requires": { + "numeric": "^1.2.6" + } + }, "@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -13123,9 +13145,9 @@ } }, "@next/env": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", - "integrity": "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==" + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.1.tgz", + "integrity": "sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==" }, "@next/eslint-plugin-next": { "version": "14.1.0", @@ -13176,57 +13198,57 @@ } }, "@next/swc-darwin-arm64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz", - "integrity": "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz", + "integrity": "sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==", "optional": true }, "@next/swc-darwin-x64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", - "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz", + "integrity": "sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==", "optional": true }, "@next/swc-linux-arm64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", - "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz", + "integrity": "sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==", "optional": true }, "@next/swc-linux-arm64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", - "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz", + "integrity": "sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==", "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", - "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz", + "integrity": "sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", - "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz", + "integrity": "sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==", "optional": true }, "@next/swc-win32-arm64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", - "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz", + "integrity": "sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==", "optional": true }, "@next/swc-win32-ia32-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", - "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz", + "integrity": "sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==", "optional": true }, "@next/swc-win32-x64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", - "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz", + "integrity": "sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==", "optional": true }, "@nodelib/fs.scandir": { @@ -14494,12 +14516,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-process-hrtime": { @@ -16328,9 +16350,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -17939,20 +17961,20 @@ "dev": true }, "next": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", - "integrity": "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==", - "requires": { - "@next/env": "14.1.0", - "@next/swc-darwin-arm64": "14.1.0", - "@next/swc-darwin-x64": "14.1.0", - "@next/swc-linux-arm64-gnu": "14.1.0", - "@next/swc-linux-arm64-musl": "14.1.0", - "@next/swc-linux-x64-gnu": "14.1.0", - "@next/swc-linux-x64-musl": "14.1.0", - "@next/swc-win32-arm64-msvc": "14.1.0", - "@next/swc-win32-ia32-msvc": "14.1.0", - "@next/swc-win32-x64-msvc": "14.1.0", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.1.tgz", + "integrity": "sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==", + "requires": { + "@next/env": "14.1.1", + "@next/swc-darwin-arm64": "14.1.1", + "@next/swc-darwin-x64": "14.1.1", + "@next/swc-linux-arm64-gnu": "14.1.1", + "@next/swc-linux-arm64-musl": "14.1.1", + "@next/swc-linux-x64-gnu": "14.1.1", + "@next/swc-linux-x64-musl": "14.1.1", + "@next/swc-win32-arm64-msvc": "14.1.1", + "@next/swc-win32-ia32-msvc": "14.1.1", + "@next/swc-win32-x64-msvc": "14.1.1", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -18034,6 +18056,11 @@ "set-blocking": "^2.0.0" } }, + "numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", @@ -20053,9 +20080,9 @@ "devOptional": true }, "ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "optional": true, "requires": {} }, diff --git a/dashboard/package.json b/dashboard/package.json index 1e84bcb8abb2d..a3716f7802ccf 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -13,6 +13,7 @@ "clean": "rm -rf .next/ && rm -rf out/" }, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", @@ -33,7 +34,7 @@ "fabric": "^5.2.1", "framer-motion": "^6.5.1", "lodash": "^4.17.21", - "next": "^14.1.0", + "next": "^14.1.1", "nuqs": "^1.14.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/dashboard/pages/await_tree.tsx b/dashboard/pages/await_tree.tsx index 2fb03afa3fc46..6babcccf7e91a 100644 --- a/dashboard/pages/await_tree.tsx +++ b/dashboard/pages/await_tree.tsx @@ -86,8 +86,16 @@ export default function AwaitTreeDump() { .entries() .map(([k, v]) => `[Barrier ${k}]\n${v}`) .join("\n") + const barrierWorkerState = _(response.barrierWorkerState) + .entries() + .map(([k, v]) => `[BarrierWorkerState (Worker ${k})]\n${v}`) + .join("\n") + const jvmStackTraces = _(response.jvmStackTraces) + .entries() + .map(([k, v]) => `[JVM (Worker ${k})]\n${v}`) + .join("\n") - result = `${title}\n\n${actorTraces}\n${rpcTraces}\n${compactionTraces}\n${barrierTraces}` + result = `${title}\n\n${actorTraces}\n${rpcTraces}\n${compactionTraces}\n${barrierTraces}\n${barrierWorkerState}\n\n${jvmStackTraces}` } catch (e: any) { result = `${title}\n\nERROR: ${e.message}\n${e.cause}` } diff --git a/dashboard/pages/subscriptions.tsx b/dashboard/pages/subscriptions.tsx new file mode 100644 index 0000000000000..b2daa38c3f95c --- /dev/null +++ b/dashboard/pages/subscriptions.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + * + */ + +import { Column, Relations } from "../components/Relations" +import { getSubscriptions } from "../lib/api/streaming" +import { Subscription as RwSubscription } from "../proto/gen/catalog" + +export default function Subscriptions() { + const subscriptionRetentionSeconds: Column = { + name: "Retention Seconds", + width: 3, + content: (r) => r.retentionSeconds ?? "unknown", + } + + const subscriptionDependentTableId: Column = { + name: "Dependent Table Id", + width: 3, + content: (r) => r.dependentTableId ?? "unknown", + } + return Relations("Subscriptions", getSubscriptions, [ + subscriptionRetentionSeconds, + subscriptionDependentTableId, + ]) +} diff --git a/develop/nix/devshell.nix b/develop/nix/devshell.nix new file mode 100644 index 0000000000000..13aad08c8b711 --- /dev/null +++ b/develop/nix/devshell.nix @@ -0,0 +1,60 @@ +{ inputs +, ... +}: + +{ + perSystem = { pkgs, lib, ... }: { + devshells.default = + let + rust-toolchain = with pkgs; + [ + ((rust-bin.fromRustupToolchainFile ../../rust-toolchain).override { + extensions = [ "rust-src" "rust-analyzer" ]; + }) + ]; + in + { + imports = [ + "${inputs.devshell}/extra/language/rust.nix" + ]; + language.rust.enableDefaultToolchain = false; + packages = rust-toolchain + # See the dependencies list in docs/developer-guide.md + ++ (with pkgs; [ + gcc + lld + protobuf + pkg-config + cyrus_sasl.out + + gnumake + cmake + maven + jdk17_headless + + tmux + postgresql + patchelf + ]); + env = [ + { + name = "PKG_CONFIG_PATH"; + value = lib.concatStringsSep ":" ( + map (pkg: "${pkg}/lib/pkgconfig") (with pkgs; [ + openssl.dev + cyrus_sasl.dev + ]) + ); + } + { + name = "LD_LIBRARY_PATH"; + value = lib.makeLibraryPath (with pkgs; [ + openssl + libgcc.lib + cyrus_sasl.out + ]); + } + ]; + }; + }; +} diff --git a/develop/nix/flake.lock b/develop/nix/flake.lock new file mode 100644 index 0000000000000..8ef4b42e5ed89 --- /dev/null +++ b/develop/nix/flake.lock @@ -0,0 +1,188 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1711099426, + "narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", + "owner": "numtide", + "repo": "devshell", + "rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1704161960, + "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "63143ac2c9186be6d9da6035fa22620018c85932", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1711703276, + "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1712439257, + "narHash": "sha256-aSpiNepFOMk9932HOax0XwNxbA38GOUVOiXfUVPOrck=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ff0dbd94265ac470dda06a657d5fe49de93b4599", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs_2", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712628742, + "narHash": "sha256-FIAlt8mbPUs8jRuh6xpFtYzDsyHzmiLNPcen8HwvD00=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "e7354bb9e5f68b2074e272fd5f5ac3f4848860ba", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/develop/nix/flake.nix b/develop/nix/flake.nix new file mode 100644 index 0000000000000..e8c02d241c5eb --- /dev/null +++ b/develop/nix/flake.nix @@ -0,0 +1,64 @@ +# A nix flake that sets up a complete RisingWave development environment. +# +# You must have already installed Nix (https://nixos.org) on your system to use this. +# Nix can be installed on Linux or MacOS; NixOS is not required. Windows is not +# directly supported, but Nix can be installed inside of WSL2 or even Docker +# containers. Please refer to https://nixos.org/download for details. +# +# You must also enable support for flakes in Nix. See the following for how to +# do so permanently: https://nixos.wiki/wiki/Flakes#Enable_flakes +# +# Usage: +# +# With Nix installed, navigate to the directory containing this flake and run +# `nix develop ./develop/nix`. +# +# You should now be dropped into a new shell with all programs and dependencies +# available to you! +# +# You can exit the development shell by typing `exit`, or using Ctrl-D. +# +# If you would like this development environment to activate automatically +# upon entering this directory in your terminal, first install `direnv` +# (https://direnv.net/). Then run `echo 'use flake ./develop/nix' >> .envrc` at +# the root of the RisingWave repo. Finally, run `direnv allow` to allow the +# contents of '.envrc' to run every time you enter this directory. Voilà! +# +# note: If you don't want to see git untracked .envrc files bother you, +# you can run `echo '.envrc' >> .git/info/exclude` in the root of project +# to make it ignored locally. + +{ + description = '' + RisingWave the Cloud-native SQL stream processing, analytics, and management. + ''; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + + devshell.url = "github:numtide/devshell"; + + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ + inputs.devshell.flakeModule + ./overlays.nix + ./devshell.nix + ]; + + systems = [ + "x86_64-linux" + "aarch64-linux" + "aarch64-darwin" + "x86_64-darwin" + ]; + }; +} + diff --git a/develop/nix/overlays.nix b/develop/nix/overlays.nix new file mode 100644 index 0000000000000..f37c8312cb44e --- /dev/null +++ b/develop/nix/overlays.nix @@ -0,0 +1,14 @@ +{ inputs +, ... +}: + +{ + perSystem = { pkgs, system, ... }: { + _module.args.pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ + (import inputs.rust-overlay) + ]; + }; + }; +} diff --git a/docker/Dockerfile b/docker/Dockerfile index 3d902bc2ffc6c..4cd5c5157dc42 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,19 +1,13 @@ -FROM ubuntu:22.04 AS base +FROM ubuntu:24.04 AS base ENV LANG en_US.utf8 RUN apt-get update \ - && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-11-jdk software-properties-common - -# Install Python 3.12 -RUN add-apt-repository ppa:deadsnakes/ppa -y && \ - apt-get update -yy && \ - DEBIAN_FRONTEND=noninteractive apt-get install python3.12 python3.12-dev -yy -ENV PYO3_PYTHON=python3.12 + && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-17-jdk software-properties-common python3.12 python3.12-dev openssl pkg-config FROM base AS rust-base -RUN apt-get update && apt-get -y install make cmake protobuf-compiler curl bash lld unzip +RUN apt-get update && apt-get -y install make cmake protobuf-compiler curl bash lld unzip rsync # Install Node.js as dependency for building the dashboard. # Bump version together with `dashboard/.node-version`. @@ -73,9 +67,10 @@ COPY ./ /risingwave WORKDIR /risingwave ENV ENABLE_BUILD_DASHBOARD=1 +ENV OPENSSL_STATIC=1 RUN cargo fetch && \ - cargo build -p risingwave_cmd_all --release --features "rw-static-link" --features embedded-deno-udf --features embedded-python-udf && \ + cargo build -p risingwave_cmd_all --release --features "rw-static-link" --features all-udf && \ mkdir -p /risingwave/bin && \ mv /risingwave/target/release/risingwave /risingwave/bin/ && \ mv /risingwave/target/release/risingwave.dwp /risingwave/bin/ && \ @@ -124,9 +119,14 @@ LABEL org.opencontainers.image.source https://github.com/risingwavelabs/risingwa RUN apt-get update && apt-get -y install linux-tools-generic \ && ln -s "$(find /usr/lib/linux-tools/*/perf | head -1)" /usr/local/bin/perf -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install gdb libpam-krb5 krb5-user telnet kafkacat \ +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install gdb libpam-krb5 krb5-user telnet kafkacat rustup \ && rm -rf /var/lib/{apt,dpkg,cache,log}/ +# Set default Rust toolchain but don't install it to keep the image size small +# The toolchain will be installed when it is first used +# Do not use `rustup default stable` because it will install immediately +RUN rustup show && sed -i '1s/^/default_toolchain = "stable"\n/' ~/.rustup/settings.toml + RUN mkdir -p /risingwave/bin/connector-node && mkdir -p /risingwave/lib COPY --from=rust-builder /risingwave/bin/risingwave /risingwave/bin/risingwave @@ -142,4 +142,4 @@ ENV CONNECTOR_LIBS_PATH /risingwave/bin/connector-node/libs ENV IN_CONTAINER=1 ENTRYPOINT [ "/risingwave/bin/risingwave" ] -CMD [ "playground" ] +CMD [ "single_node" ] diff --git a/docker/Dockerfile.hdfs b/docker/Dockerfile.hdfs index 53a6da30fe6e0..b6eba07c421c0 100644 --- a/docker/Dockerfile.hdfs +++ b/docker/Dockerfile.hdfs @@ -1,16 +1,9 @@ -FROM ubuntu:22.04 AS base +FROM ubuntu:24.04 AS base ENV LANG en_US.utf8 RUN apt-get update \ - && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-11-jdk software-properties-common - -# Install Python 3.12 -RUN add-apt-repository ppa:deadsnakes/ppa -y && \ - apt-get update -yy && \ - DEBIAN_FRONTEND=noninteractive apt-get install python3.12 python3.12-dev -yy -ENV PYO3_PYTHON=python3.12 - + && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-17-jdk software-properties-common python3.12 python3.12-dev openssl pkg-config FROM base AS dashboard-builder @@ -104,8 +97,10 @@ ARG JAVA_HOME_PATH ENV JAVA_HOME ${JAVA_HOME_PATH} ENV LD_LIBRARY_PATH ${JAVA_HOME_PATH}/lib/server:${LD_LIBRARY_PATH} +ENV OPENSSL_STATIC=1 + RUN cargo fetch && \ - cargo build -p risingwave_cmd_all --release -p risingwave_object_store --features hdfs-backend --features "rw-static-link" --features embedded-deno-udf --features embedded-python-udf && \ + cargo build -p risingwave_cmd_all --release -p risingwave_object_store --features hdfs-backend --features "rw-static-link" --features all-udf && \ mkdir -p /risingwave/bin && \ mv /risingwave/target/release/risingwave /risingwave/bin/ && \ mv /risingwave/target/release/risingwave.dwp /risingwave/bin/ && \ @@ -117,8 +112,8 @@ RUN cd /risingwave/java && mvn -B package -Dmaven.test.skip=true -Dno-build-rust mkdir -p /risingwave/bin/connector-node && \ tar -zxvf /risingwave/java/connector-node/assembly/target/risingwave-connector-1.0.0.tar.gz -C /risingwave/bin/connector-node -FROM ubuntu:22.04 as image-base -RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install ca-certificates openjdk-11-jdk wget libsasl2-dev && rm -rf /var/lib/{apt,dpkg,cache,log}/ +FROM ubuntu:24.04 as image-base +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install ca-certificates openjdk-17-jdk wget libsasl2-dev && rm -rf /var/lib/{apt,dpkg,cache,log}/ FROM image-base as risingwave LABEL org.opencontainers.image.source https://github.com/risingwavelabs/risingwave diff --git a/docker/aws/Dockerfile b/docker/aws/Dockerfile index b1ada55db4816..1de5f1c7cce08 100644 --- a/docker/aws/Dockerfile +++ b/docker/aws/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:24.04 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install ca-certificates && rm -rf /var/lib/{apt,dpkg,cache,log}/ RUN mkdir -p /risingwave/bin diff --git a/docker/dashboards/risingwave-dev-dashboard.json b/docker/dashboards/risingwave-dev-dashboard.json index 0ca7142657562..3e303bca36ae2 100644 --- a/docker/dashboards/risingwave-dev-dashboard.json +++ b/docker/dashboards/risingwave-dev-dashboard.json @@ -1 +1 @@ -{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dev Dashboard","editable":true,"gnetId":null,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":5,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (total) - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (avg per core) - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"RW cluster can configure multiple meta nodes to achieve high availability. One is the leader and the rest are the followers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(meta_num{job=~\"$job\",instance=~\"$node\"}) by (worker_addr,role)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_addr}} @ {{role}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Meta Cluster","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Cluster Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":9,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The rate of successful recovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery Successful Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of failed reocovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Failed recovery attempts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Time spent in a successful recovery attempt","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":12,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency pmax - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by (le) (rate(recovery_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by (le) (rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Recovery","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":3},"height":null,"hideTimeOverride":false,"id":13,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of barriers that have been ingested but not completely processed. This metric reflects the current level of congestion within the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all_barrier","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"in_flight_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"in_flight_barrier","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":15,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration from the last committed barrier's epoch time to the current time. This metric reflects the data freshness of the system. During this time, no new data has been committed.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":16,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"timestamp(last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}) - last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_pending_time","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier pending time (secs)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(rows).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":18,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_partition_input_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragmend_id={{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":19,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(MB/s).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragmend_id={{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Monitor each source upstream, 0 means the upstream is not normal, 1 means the source is ready.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Upstream Status","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Source Split Change Events frequency by source_id and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_split_change_event_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Split Change Events frequency(events/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka Consumer Lag Size by source_id, partition and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min(source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"} - on(source_id, partition) group_right() source_latest_message_id{job=~\"$job\",instance=~\"$node\"}, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka Consumer Lag Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}} - actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":27,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":28,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, table_id) * on(fragment_id, table_id) group_left(table_name) table_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}} - fragment_id {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration between the time point when the scheduled barrier needs to be sent and the time point when the barrier gets actually sent to all the compute nodes. Developers can thus detect any internal congestion.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":31,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_send_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_send_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Send Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":32,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"max(sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier In-Flight Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p999 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_pmax - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_avg - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Sync Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_wait_commit_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_wait_commit_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Wait Commit Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of actors that have processed the earliest in-flight barriers per second. This metric helps users to detect potential congestion or stuck in the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_barrier_manager_progress{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Earliest In-Flight Barrier Progress","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":4},"height":null,"hideTimeOverride":false,"id":36,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the cdc backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the cdc backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":38,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":39,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p50 - {{table_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p99 - {{table_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag pmax - {{table_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Consume Lag Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(cdc_source_error{job=~\"$job\",instance=~\"$node\"}) by (connector_name, source_id, error_msg)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{connector_name}}: {{error_msg}} ({{source_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Source Errors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming CDC","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":5},"height":null,"hideTimeOverride":false,"id":41,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_input_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":44,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":45,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The operator-level memory usage statistics collected by each LRU cache","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":46,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (table_id, desc)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} desc: {{desc}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Memory usage aggregated by materialized views","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage of Materialized Views","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialize Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_left_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache left miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_right_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache right miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":51,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize executor cache miss ratio - table {{table_id}} fragment {{fragment_id}} {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_left_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache left miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_right_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache right miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":52,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p999 - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, fragment_id, wait_side, job)(rate(stream_join_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,fragment_id,wait_side,job) (rate(stream_join_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Executor Barrier Align","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":54,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id,side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Match Duration Per Second","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Multiple rows with distinct primary keys may have the same join key. This metric counts the number of join keys in the executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":55,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (fragment_id, side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of matched rows on the opposite side","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":56,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, actor_id, table_id) (rate(stream_join_matched_join_keys_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Executor Matched Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":57,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} actor {{actor_id}}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Executor Cache Statistics For Each StreamChunk","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":58,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":59,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The total heap size of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":60,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Heap Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each top_n executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":61,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"TopN Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in temporal join executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":62,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Cache Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in lookup executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":63,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lookup Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in over window executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":64,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_range_cache_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window partition range cache entry count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When enabled, this metric shows the input throughput of each executor.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":65,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_identity, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The actor-level memory usage statistics reported by TaskLocalAlloc. (Disabled by default)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":66,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(actor_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"actor_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Memory Usage (TaskLocalAlloc)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":6},"height":null,"hideTimeOverride":false,"id":67,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":68,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_actor_execution_time{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Execution Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":69,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":70,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":8},"height":null,"hideTimeOverride":false,"id":71,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":8},"height":null,"hideTimeOverride":false,"id":72,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":73,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":16},"height":null,"hideTimeOverride":false,"id":74,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":16},"height":null,"hideTimeOverride":false,"id":75,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":76,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":24},"height":null,"hideTimeOverride":false,"id":77,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":24},"height":null,"hideTimeOverride":false,"id":78,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":79,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":32},"height":null,"hideTimeOverride":false,"id":80,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":32},"height":null,"hideTimeOverride":false,"id":81,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":82,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":40},"height":null,"hideTimeOverride":false,"id":83,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Avg Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors (Tokio)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":7},"height":null,"hideTimeOverride":false,"id":84,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":85,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Send Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":86,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Recv Throughput","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Exchange","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":87,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during computation. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":88,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compute Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during source data ingestion. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":89,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during data sink out. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":90,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Errors by Type","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Streaming Errors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":9},"height":null,"hideTimeOverride":false,"id":91,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":92,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Exchange Recv Row Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":93,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_task_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mpp Task Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"All memory usage of batch executors in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":94,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"compute_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mem Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":95,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_heartbeat_worker_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Heartbeat Worker Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":96,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Row SeqScan Next Duration","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":97,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":98,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{table_id}} @ {{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Hummock has three parts of memory usage: 1. Meta Cache 2. Block CacheThis metric shows the real memory usage of each of these three caches.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":99,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"data cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":100,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='meta_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='data_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='data_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":101,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_scan_key_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type, table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{table_id}} @ {{type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iter keys flow","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":102,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p50 - {{table_id}} @ {{job}} @ {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p99 - {{table_id}} @ {{job}} @ {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts pmax - {{table_id}} @ {{job}} @ {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Merged SSTs","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the latency of Get operations that have been issued to the state store.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":103,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_get_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the time spent on iterator initialization.Histogram of the time spent on iterator scanning.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":104,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_init_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_iter_init_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_scan_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_iter_scan_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":105,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive count - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive count - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter check count- {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":106,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive rate - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"False-Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":107,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(((sum(rate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read req bloom filter false positive rate - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter False-Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":108,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_slow_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":109,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_shared_buffer_hit_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer hit - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_in_process_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":110,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":111,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":112,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p50 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p99 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read pmax - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Read Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":113,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Count - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of a single key-value pair when reading by operation Get.Operation Get gets a single key-value pair with respect to a caller-specified key. If the key does not exist in the storage, the size of key is counted into this metric and the size of value is 0.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":114,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance) + sum(rate(state_store_get_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of all the key-value paris when reading by operation Iter.Operation Iter scans a range of key-value pairs.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":115,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":116,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":117,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Read)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":11},"height":null,"hideTimeOverride":false,"id":118,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the real memory usage of uploader.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":119,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading memory - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task size - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader Memory Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of time spent on compacting shared buffer to remote storage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":120,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 upload task duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 upload task duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax upload task duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Build and Sync Sstable Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":121,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p50 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p99 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write pmax - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Write Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":122,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_merge_imm_task_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"merge imm tasks - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_spill_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploader spill tasks - {{uploader_stage}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_syncing_epoch_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"syncing epoch count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Tasks Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":123,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_merge_imm_memory_sz{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Merging tasks memory size - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_spill_task_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploading tasks size - {{uploader_stage}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Task Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":124,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write batch - {{table_id}} @ {{job}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"l0 - {{job}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":125,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":126,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_write_batch_tuple_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write_batch_kv_pair_count - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Item Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":127,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_size_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) / sum(rate(state_store_write_batch_size_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_shared_buffer_to_sstable_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) / sum(rate(compactor_shared_buffer_to_sstable_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sync - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the statistics of mem_table size on flush. By default only max (p100) is shown.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":128,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_id, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_write_batch_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, table_id, job, instance) (rate(state_store_write_batch_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":129,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_mem_table_spill_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mem table spill table id - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Mem Table Spill Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":130,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Sync Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":131,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_event_handler_pending_event{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handler pending event number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":132,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 {{event_type}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 {{event_type}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax {{event_type}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax finished_task_wait_poll {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handle latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Write)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":12},"height":null,"hideTimeOverride":false,"id":133,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":134,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size(KB) of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":135,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Size(KB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The of bytes that have been written by commit epoch per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":136,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_commit_write_throughput{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{table_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Flush Bytes by Table","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":137,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result!='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Failure Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":138,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Success Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have been skipped.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":139,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_skip_compact_frequency{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (level, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{level}}-{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Skip Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg l0 select_level_count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":140,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_l0_compact_level_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_l0_compact_level_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task L0 Select Level Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg file count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":141,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_compact_task_file_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_compact_task_file_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task File Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The distribution of the compact task size triggered, including p90 and max. and categorize it according to different cg, levels and task types.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":142,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task Size Distribution","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that are running.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":143,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_num{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_parallelism{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_pending_parallelism - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Running Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compact-task: The total time have been spent on compaction.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":144,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p50 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(compute_refill_cache_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compute_apply_version_duration_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(compactor_compact_task_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(compactor_compact_task_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(state_store_compact_sst_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(state_store_compact_sst_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"KBs read from next level during history compactions to next level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":145,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job) + sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_fast_compact_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fast compact - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Write refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":146,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Bytes(GiB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Write amplification is the amount of bytes written to the remote storage by compaction for each one byte of flushed SSTable data. Write amplification is by definition higher than 1.0 because we write each piece of data to L0, and then write it again to an SSTable, and then compaction may read this piece of data and write it to a new SSTable, that's another write.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":147,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) / sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"})","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write amplification","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Amplification","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables that is being compacted at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":148,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"num of compact_task","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":149,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_task_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":150,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"KBs Read/Write by Level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":151,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_write_sstn{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Count of SSTs Read/Write by level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_bloom_filter, for observing bloom_filter size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":152,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_meta - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_file_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_file_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_file - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_avg_key_size, for observing sstable_avg_key_size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":153,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_key_size - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_value_size - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Item Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg count gotten from sstable_distinct_epoch_count, for observing sstable_distinct_epoch_count","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":154,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_epoch_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Stat","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total time of operations which read from remote storage when enable prefetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":155,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Remote Read Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":156,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_iter_scan_key_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{type}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Iter keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"bytes of Lsm tree needed to reach balance","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":157,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_pending_bytes{job=~\"$job\",instance=~\"$node\"}) by (instance, group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact pending bytes - {{group}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Compact Pending Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compression ratio of each level of the lsm tree","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":158,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_level_compression_ratio{job=~\"$job\",instance=~\"$node\"}) by (instance, group, level, algorithm)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lsm compression ratio - cg{{group}} @ L{{level}} - {{algorithm}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Level Compression Ratio","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Compaction","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":13},"height":null,"hideTimeOverride":false,"id":159,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":160,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":161,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, type, job, instance)(rate(object_store_operation_latency_sum{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(object_store_operation_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":162,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type!~'streaming_upload_write_bytes|streaming_read_read_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'upload|delete',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-write - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'read|readv|list|metadata',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-read - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":163,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":164,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Failure Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":165,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(aws_sdk_retry_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(s3_read_request_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Retry Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"There are two types of operations: 1. GET, SELECT, and DELETE, they cost 0.0004 USD per 1000 requests. 2. PUT, COPY, POST, LIST, they cost 0.005 USD per 1000 requests.Reading from S3 across different regions impose extra cost. This metric assumes 0.01 USD per 1GB data transfer. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":166,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}) * 0.01 / 1000 / 1000 / 1000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"(Cross Region) Data Transfer Cost","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'read|streaming_read_start|delete',job=~\"$job\",instance=~\"$node\"}) * 0.0004 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GET, SELECT, and all other Requests Cost","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|delete_objects|list',job=~\"$job\",instance=~\"$node\"}) * 0.005 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"PUT, COPY, POST, LIST Requests Cost","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Realtime)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric uses the total size of data in S3 at this second to derive the cost of storing data for a whole month. The price is 0.023 USD per GB. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":167,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance) * 0.023 / 1000 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Monthly Storage Cost","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Monthly)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Object Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":14},"height":null,"hideTimeOverride":false,"id":168,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":169,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, extra, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":170,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":171,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, extra, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":172,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_duration_count{op=\"lookup\",extra=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, instance) / (sum(rate(foyer_storage_op_duration_count{op=\"lookup\",extra=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, instance) + sum(rate(foyer_storage_op_duration_count{op=\"lookup\",extra=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache hit ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":173,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=~\"meta|data\",op!~\"filtered|ignored\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":174,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Data Refill Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":175,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":176,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(refill_queue_total) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"refill queue length @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Queue Length","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":177,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(foyer_storage_total_bytes{job=~\"$job\",instance=~\"$node\"}) by (foyer, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} size @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":178,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inner Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":179,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_slow_op_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, extra, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":180,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":181,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":182,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"parent_meta\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup hit ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":183,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":184,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"unit_inheritance\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":185,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":186,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",op=\"success\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / sum(rate(refill_total{type=\"block\",op=\"unfiltered\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ratio","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Tiered Cache","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":15},"height":null,"hideTimeOverride":false,"id":187,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":188,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p50 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p99 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time pmax - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lock Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":189,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p50 - {{method}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p99 - {{method}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time pmax - {{method}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Real Process Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":190,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version size","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":191,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"current version id","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"checkpoint version id","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned version id","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_safepoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min safepoint version id","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Id","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":192,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"max committed epoch","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_safe_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"safe epoch","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned epoch","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":193,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_value_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":194,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":195,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_count',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table KV Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\nObjects are classified into 3 groups:\n- not referenced by versions: these object are being deleted from object store.\n- referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n- referenced by current version: these objects are in the latest version.\n\nAdditionally, a metric on all objects (including dangling ones) is updated with low-frequency. The metric is updated right before full GC. So subsequent full GC may reduce the actual value significantly, without updating the metric.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":196,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects (including dangling ones)","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Refer to `Object Total Number` panel for classification of objects.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":197,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects, including dangling ones","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of hummock version delta log","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":198,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_delta_log_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"delta log total number","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Delta Log Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"hummock version checkpoint latency","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":199,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(storage_version_checkpoint_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(storage_version_checkpoint_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Checkpoint Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When certain per compaction group threshold is exceeded (e.g. number of level 0 sub-level in LSMtree), write op to that compaction group is stopped temporarily. Check log for detail reason of write stop.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":200,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compaction_group_{{compaction_group_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Stop Compaction Groups","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of attempts to trigger full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":201,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_trigger_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_trigger_count","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Trigger Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"the object id watermark used in last full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":202,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_last_object_id_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_last_object_id_watermark","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Last Watermark","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":203,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Event Loop Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The times of move_state_table occurs","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":204,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_move_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"move table cg{{group}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Move State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of state_tables in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":205,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"state table cg{{group}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of branched_sst in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":206,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_branched_sst_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"branched sst cg{{group}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Branched SST Count","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":207,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total backup job count since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":208,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"backup_job_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"job count","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Latency of backup jobs since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":209,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p50 - {{state}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p99 - {{state}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time pmax - {{state}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Process Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Backup Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":17},"height":null,"hideTimeOverride":false,"id":210,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":211,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":212,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Drop latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":213,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetCatalog latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Catalog Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":214,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":215,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"AddWorkerNode latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":216,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ListAllNodes latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Cluster Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":19},"height":null,"hideTimeOverride":false,"id":217,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":218,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CreateMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":219,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"DropMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":220,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Flush latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Stream Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":20},"height":null,"hideTimeOverride":false,"id":221,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":222,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinVersionBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":223,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinSnapshotBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":224,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ReportCompactionTasks latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":225,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetNewSstIds latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":21},"height":null,"hideTimeOverride":false,"id":226,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":227,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_counts - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":228,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_version_before_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_version_before_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"version_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":229,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latencyp90 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":230,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_counts - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_counts - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":231,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_get_new_sst_ids_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":232,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_counts - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":233,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC: Hummock Meta Client","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":22},"height":null,"hideTimeOverride":false,"id":234,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":235,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":236,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":237,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":238,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Running Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":239,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Rejected queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":240,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Completed Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":241,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":242,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Frontend","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":23},"height":null,"hideTimeOverride":false,"id":243,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":244,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(lru_runtime_loop_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager loop count per sec","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":245,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_watermark_step{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager watermark steps","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"watermark_time is the current lower watermark of cached data. physical_now is the current time of the machine. The diff (physical_now - watermark_time) shows how much data is cached.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":246,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_physical_now_ms{job=~\"$job\",instance=~\"$node\"} - lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager diff between watermark_time and now (ms)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":247,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":248,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":249,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_resident_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The resident memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":250,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_metadata_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The metadata memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":251,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":252,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":253,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"} - on() group_right() lru_evicted_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager diff between current watermark and evicted watermark time (ms) for actors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":254,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":255,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":256,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":25},"height":null,"hideTimeOverride":false,"id":257,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":258,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, connector, sink_id)(rate(sink_commit_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(sink_commit_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":259,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest write epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest read epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Read/Write Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":260,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(max(log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":261,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min((max(log_store_first_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume persistent log lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Persistent Log Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":262,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":263,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":264,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":265,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":266,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":267,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":268,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":269,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":270,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_rewind_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Rewind Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":271,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(kv_log_store_rewind_delay_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor_id, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rewind delay (second)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":272,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka high watermark by source and partition and source latest message by partition, source and actor","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":273,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"high watermark: source={{source_id}} partition={{partition}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_latest_message_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest msg: source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka high watermark and source latest message","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current number of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":274,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current total size of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":275,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Size in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages transmitted (produced) to Kafka brokers","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":276,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_tx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Produced Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages consumed, not including ignored messages (due to offset, etc), from Kafka brokers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":277,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_rx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Received Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages awaiting transmission to broker","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":278,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_outbuf_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count Pending to Transmit (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages in-flight to broker awaiting response","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":279,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_waitresp_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inflight Message Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of transmission errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":280,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_tx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Transmitting (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of receive errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":281,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Receiving (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of requests timed out","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":282,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_req_timeouts{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Timeout Request Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker latency / round-trip time in milli seconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":283,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"RTT (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker throttling time in milliseconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":284,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Throttle Time (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Age of metadata from broker for this topic (milliseconds)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":285,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_metadata_age{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Metadata_age Age","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch sizes in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":286,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch message counts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":null,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Messages","transformations":[],"transparent":false,"type":"timeseries"}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages ready to be produced in transmit queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":287,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_xmit_msgq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message to be Transmitted","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of pre-fetched messages in fetch queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":288,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_fetchq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message in pre fetch queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Next offset to fetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":289,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_next_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Next offset to fetch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Last committed offset","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":290,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_committed_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Committed Offset","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Kafka Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":27},"height":null,"hideTimeOverride":false,"id":291,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":292,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Network throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":293,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"S3 throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":294,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} write @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total write @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"gRPC throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":295,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_io_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} grpc {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"IO error rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":296,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Existing connection count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":297,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":298,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection err rate","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network connection","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":28},"height":null,"hideTimeOverride":false,"id":299,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"iceberg write qps","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":300,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_write_qps{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Qps Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":301,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, sink_id)(rate(iceberg_write_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(iceberg_write_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Latency Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":302,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_rolling_unfushed_data_file{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg rolling unfushed data file","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":303,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_position_delete_cache_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg position delete cache num","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":304,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_partition_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg partition num","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Iceberg Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":29},"height":null,"hideTimeOverride":false,"id":305,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":306,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Calls Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":307,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_input_chunk_rows_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_input_chunk_rows_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_input_chunk_rows_avg - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Input Chunk Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":308,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.50, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, link, name, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":309,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":310,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (bytes)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Defined Function","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(table_info, table_id)","description":"Reporting table id of the metric","hide":0,"includeAll":true,"label":"Table","multi":true,"name":"table","options":[],"query":{"query":"label_values(table_info, table_id)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dev_dashboard","uid":"Ecy3uV1nz","version":0} +{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dev Dashboard","editable":true,"gnetId":null,"graphTooltip":0,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Actor count per compute node","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Count (Group By Compute Node)","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":5,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","query":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Memory usage relative to k8s resource limit of container. Only works in K8s environment","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(avg by(namespace, pod) (container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\"})) / ( sum by(namespace, pod) (kube_pod_container_resource_limits{namespace=~\"$namespace\", pod=~\"$pod\", container=\"$component\", resource=\"memory\", unit=\"byte\"}))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg memory usage @ {{job}} @ {{instance}}","metric":"","query":"(avg by(namespace, pod) (container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\"})) / ( sum by(namespace, pod) (kube_pod_container_resource_limits{namespace=~\"$namespace\", pod=~\"$pod\", container=\"$component\", resource=\"memory\", unit=\"byte\"}))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory relative","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":9,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (total) - {{job}} @ {{instance}}","metric":"","query":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (job, instance) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (avg per core) - {{job}} @ {{instance}}","metric":"","query":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (job, instance) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"CPU usage relative to k8s resource limit of container. Only works in K8s environment","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\",container=~\"$component\",pod=~\"$pod\"}[$__rate_interval])) by (namespace, pod)) / (sum(kube_pod_container_resource_limits{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\", resource=\"cpu\"}) by (namespace, pod))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage @ {{job}} @ {{instance}}","metric":"","query":"(sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\",container=~\"$component\",pod=~\"$pod\"}[$__rate_interval])) by (namespace, pod)) / (sum(kube_pod_container_resource_limits{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\", resource=\"cpu\"}) by (namespace, pod))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU relative","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"RW cluster can configure multiple meta nodes to achieve high availability. One is the leader and the rest are the followers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(meta_num{job=~\"$job\",instance=~\"$node\"}) by (worker_addr,role)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_addr}} @ {{role}}","metric":"","query":"sum(meta_num{job=~\"$job\",instance=~\"$node\"}) by (worker_addr,role)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Meta Cluster","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Cluster Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":12,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The rate of successful recovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":13,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery Successful Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of failed reocovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Failed recovery attempts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Time spent in a successful recovery attempt","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":15,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency pmax - {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by (le) (rate(recovery_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by (le) (rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency avg","metric":"","query":"sum by (le) (rate(recovery_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by (le) (rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Recovery","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":3},"height":null,"hideTimeOverride":false,"id":16,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of barriers that have been ingested but not completely processed. This metric reflects the current level of congestion within the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all_barrier","metric":"","query":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"in_flight_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"in_flight_barrier","metric":"","query":"in_flight_barrier_nums{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":18,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","query":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration from the last committed barrier's epoch time to the current time. This metric reflects the data freshness of the system. During this time, no new data has been committed.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":19,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"timestamp(last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}) - last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_pending_time","metric":"","query":"timestamp(last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}) - last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier pending time (secs)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(rows).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_partition_input_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragment_id={{fragment_id}}","metric":"","query":"rate(source_partition_input_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(MB/s).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragment_id={{fragment_id}}","metric":"","query":"(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Monitor each source upstream, 0 means the upstream is not normal, 1 means the source is ready.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","query":"source_status_is_up{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Upstream Status","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Source Split Change Events frequency by source_id and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_split_change_event_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_source_split_change_event_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Split Change Events frequency(events/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka Consumer Lag Size by source_id, partition and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":27,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min(source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"} - on(source_id, partition) group_right() source_latest_message_id{job=~\"$job\",instance=~\"$node\"}, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","query":"clamp_min(source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"} - on(source_id, partition) group_right() source_latest_message_id{job=~\"$job\",instance=~\"$node\"}, 0)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka Consumer Lag Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":28,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","query":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}} - actor {{actor_id}}","metric":"","query":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","query":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":31,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(fragment_id, table_id) group_left(table_name) table_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}} - actor {{actor_id}} fragment_id {{fragment_id}}","metric":"","query":"rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(fragment_id, table_id) group_left(table_name) table_info{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":32,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration between the time point when the scheduled barrier needs to be sent and the time point when the barrier gets actually sent to all the compute nodes. Developers can thus detect any internal congestion.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_send_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_send_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_avg","metric":"","query":"rate(meta_barrier_send_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_send_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Send Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"max(sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_avg","metric":"","query":"max(sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier In-Flight Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":36,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p999 - {{instance}}","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_pmax - {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_avg - {{instance}}","metric":"","query":"sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Sync Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_wait_commit_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_wait_commit_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_avg","metric":"","query":"rate(meta_barrier_wait_commit_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_wait_commit_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Wait Commit Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of actors that have processed the earliest in-flight barriers per second. This metric helps users to detect potential congestion or stuck in the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":38,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_barrier_manager_progress{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"rate(stream_barrier_manager_progress{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Earliest In-Flight Barrier Progress","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":4},"height":null,"hideTimeOverride":false,"id":39,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the cdc backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_cdc_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the cdc backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":41,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_cdc_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p50 - {{table_name}}","metric":"","query":"histogram_quantile(0.5, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p99 - {{table_name}}","metric":"","query":"histogram_quantile(0.99, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag pmax - {{table_name}}","metric":"","query":"histogram_quantile(1.0, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Consume Lag Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(cdc_source_error{job=~\"$job\",instance=~\"$node\"}) by (connector_name, source_id, error_msg)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{connector_name}}: {{error_msg}} ({{source_id}})","metric":"","query":"sum(cdc_source_error{job=~\"$job\",instance=~\"$node\"}) by (connector_name, source_id, error_msg)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Source Errors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming CDC","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":5},"height":null,"hideTimeOverride":false,"id":44,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":45,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","query":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":46,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_input_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","query":"avg(rate(stream_actor_input_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","query":"sum(rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The operator-level memory usage statistics collected by each LRU cache","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (table_id, desc)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} desc: {{desc}}","metric":"","query":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (table_id, desc)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","query":"stream_memory_usage{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Memory usage aggregated by materialized views","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{materialized_view_id}}","metric":"","query":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage of Materialized Views","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":51,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":52,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialize Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_range_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_left_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache left miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_range_cache_left_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_right_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache right miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_range_cache_right_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":54,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} fragment {{fragment_id}}","metric":"","query":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize executor cache miss ratio - table {{table_id}} fragment {{fragment_id}} {{instance}}","metric":"","query":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_over_window_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_left_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache left miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_over_window_range_cache_left_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_right_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache right miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_over_window_range_cache_right_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":55,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p999 - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, executor, fragment_id, wait_side, job)(rate(stream_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,executor,fragment_id,wait_side,job) (rate(stream_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"sum by(le, executor, fragment_id, wait_side, job)(rate(stream_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,executor,fragment_id,wait_side,job) (rate(stream_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Barrier Align","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":56,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p999 - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, fragment_id, job)(rate(stream_merge_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,fragment_id,job) (rate(stream_merge_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} - {{job}}","metric":"","query":"sum by(le, fragment_id, job)(rate(stream_merge_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,fragment_id,job) (rate(stream_merge_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Merger Barrier Align","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":57,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","query":"avg(rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":58,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id,side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","query":"avg(rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id,side)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","query":"rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Match Duration Per Second","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Multiple rows with distinct primary keys may have the same join key. This metric counts the number of join keys in the executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":59,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (fragment_id, side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","query":"sum(stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (fragment_id, side)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","query":"stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of matched rows on the opposite side","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":60,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Executor Matched Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":61,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} actor {{actor_id}}}","metric":"","query":"rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Executor Cache Statistics For Each StreamChunk","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":62,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":63,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The total heap size of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":64,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Heap Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each top_n executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":65,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"TopN Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in temporal join executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":66,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Cache Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in lookup executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":67,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lookup Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in over window executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":68,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_range_cache_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window partition range cache entry count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_over_window_range_cache_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When enabled, this metric shows the input throughput of each executor.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":69,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_identity, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_identity, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} actor {{actor_id}}","metric":"","query":"rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The actor-level memory usage statistics reported by TaskLocalAlloc. (Disabled by default)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":96},"height":null,"hideTimeOverride":false,"id":70,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(actor_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","query":"sum(actor_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"actor_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"actor_memory_usage{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Memory Usage (TaskLocalAlloc)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":6},"height":null,"hideTimeOverride":false,"id":71,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":72,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_actor_execution_time{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_actor_execution_time{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Execution Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":73,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":74,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":8},"height":null,"hideTimeOverride":false,"id":75,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":8},"height":null,"hideTimeOverride":false,"id":76,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":77,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":16},"height":null,"hideTimeOverride":false,"id":78,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":16},"height":null,"hideTimeOverride":false,"id":79,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":80,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":24},"height":null,"hideTimeOverride":false,"id":81,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":24},"height":null,"hideTimeOverride":false,"id":82,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":83,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":32},"height":null,"hideTimeOverride":false,"id":84,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":32},"height":null,"hideTimeOverride":false,"id":85,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":86,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":40},"height":null,"hideTimeOverride":false,"id":87,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Avg Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors (Tokio)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":7},"height":null,"hideTimeOverride":false,"id":88,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":89,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","query":"rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Send Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":90,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","query":"rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Recv Throughput","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Exchange","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":91,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during computation. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":92,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","query":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compute Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during source data ingestion. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":93,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during data sink out. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":94,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Errors by Type","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Streaming Errors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":9},"height":null,"hideTimeOverride":false,"id":95,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":96,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","query":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Exchange Recv Row Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":97,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_task_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"batch_task_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mpp Task Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"All memory usage of batch executors in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":98,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"compute_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"compute_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"frontend_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mem Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":99,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_heartbeat_worker_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"batch_heartbeat_worker_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Heartbeat Worker Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":100,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next avg - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Row SeqScan Next Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Disk throughputs of spilling-out in the bacth query engine","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":101,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(batch_spill_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}} @ {{instance}}","metric":"","query":"sum(rate(batch_spill_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(batch_spill_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}} @ {{instance}}","metric":"","query":"sum(rate(batch_spill_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Spill Throughput","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":102,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":103,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{table_id}} @ {{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Hummock has three parts of memory usage: 1. Meta Cache 2. Block CacheThis metric shows the real memory usage of each of these three caches.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":104,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache - {{job}} @ {{instance}}","metric":"","query":"avg(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"data cache - {{job}} @ {{instance}}","metric":"","query":"avg(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_prefetch_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"prefetch cache - {{job}} @ {{instance}}","metric":"","query":"avg(state_store_prefetch_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":105,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='meta_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"(sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='meta_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='data_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='data_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"(sum(rate(state_store_sst_store_block_request_counts{type='data_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='data_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the estimated hit ratio of a block while in the block cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":106,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.1, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p10 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.1, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.25, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p25 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.25, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.5, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p50 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.5, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.75, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p75 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.75, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.9, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p90 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.9, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(1.0, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p100 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(1.0, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Cache Efficiency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":107,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_scan_key_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type, table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{table_id}} @ {{type}} @ {{instance}}","metric":"","query":"sum(rate(state_store_iter_scan_key_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type, table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iter keys flow","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":108,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p50 - {{table_id}} @ {{job}} @ {{type}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p99 - {{table_id}} @ {{job}} @ {{type}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts pmax - {{table_id}} @ {{job}} @ {{type}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Merged SSTs","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the latency of Get operations that have been issued to the state store.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":109,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_get_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id)(rate(state_store_get_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the time spent on iterator initialization.Histogram of the time spent on iterator scanning.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":110,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_init_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_init_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time avg - {{iter_type}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(state_store_iter_init_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_init_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_scan_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_scan_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time avg - {{iter_type}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(state_store_iter_scan_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_scan_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":111,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive count - {{table_id}} - {{type}}","metric":"","query":"sum(irate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive count - {{table_id}} - {{type}}","metric":"","query":"sum(irate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter check count- {{table_id}} - {{type}}","metric":"","query":"sum(irate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":112,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive rate - {{table_id}} - {{type}}","metric":"","query":"(sum(rate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"False-Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":113,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(((sum(rate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read req bloom filter false positive rate - {{table_id}} - {{type}}","metric":"","query":"(((sum(rate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter False-Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":114,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_slow_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"state_store_iter_slow_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":115,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_shared_buffer_hit_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer hit - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_get_shared_buffer_hit_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id, iter_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{iter_type}} - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_iter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id, iter_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":116,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":117,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":118,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p50 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p99 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read pmax - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Read Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":119,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_in_progress_counts{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Existing {{iter_type}} count @ {{table_id}}","metric":"","query":"state_store_iter_in_progress_counts{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_log_op_type_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, op_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter_log op count @ {{table_id}} {{op_type}}","metric":"","query":"sum(rate(state_store_iter_log_op_type_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, op_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Count - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of a single key-value pair when reading by operation Get.Operation Get gets a single key-value pair with respect to a caller-specified key. If the key does not exist in the storage, the size of key is counted into this metric and the size of value is 0.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":120,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance) + sum(rate(state_store_get_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_get_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance) + sum(rate(state_store_get_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of all the key-value paris when reading by operation Iter.Operation Iter scans a range of key-value pairs.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":121,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_iter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":122,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":123,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"state_store_iter_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Read)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":11},"height":null,"hideTimeOverride":false,"id":124,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the real memory usage of uploader.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":125,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading memory - {{job}} @ {{instance}}","metric":"","query":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploader imm size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unflushed imm size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"orphan imm size - {{job}} @ {{instance}}","metric":"","query":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_old_value_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"old value size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_old_value_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader Memory Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of time spent on compacting shared buffer to remote storage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":126,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 Sync duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 Sync duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax Sync duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg Sync duration - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(state_store_sync_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 upload task duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 upload task duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax upload task duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Build and Sync Sstable Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":127,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p50 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.5, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p99 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.99, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write pmax - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Write Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":128,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_merge_imm_task_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"merge imm tasks - {{table_id}} @ {{instance}}","metric":"","query":"sum(irate(state_store_merge_imm_task_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_spill_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploader spill tasks - {{uploader_stage}} @ {{instance}}","metric":"","query":"sum(irate(state_store_spill_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task count - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_uploading_task_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_syncing_epoch_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"syncing epoch count - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_syncing_epoch_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Tasks Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":129,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_merge_imm_memory_sz{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Merging tasks memory size - {{table_id}} @ {{instance}}","metric":"","query":"sum(rate(state_store_merge_imm_memory_sz{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_spill_task_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploading tasks size - {{uploader_stage}} @ {{instance}}","metric":"","query":"sum(rate(state_store_spill_task_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Task Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":130,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write batch - {{table_id}} @ {{job}} @ {{instance}} ","metric":"","query":"sum(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"l0 - {{job}} @ {{instance}} ","metric":"","query":"sum(rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":131,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":132,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_write_batch_tuple_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write_batch_kv_pair_count - {{table_id}} @ {{instance}}","metric":"","query":"sum(irate(state_store_write_batch_tuple_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Item Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":133,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_size_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) / sum(rate(state_store_write_batch_size_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_write_batch_size_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) / sum(rate(state_store_write_batch_size_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_shared_buffer_to_sstable_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) / sum(rate(compactor_shared_buffer_to_sstable_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sync - {{job}} @ {{instance}}","metric":"","query":"sum(rate(compactor_shared_buffer_to_sstable_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) / sum(rate(compactor_shared_buffer_to_sstable_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the statistics of mem_table size on flush. By default only max (p100) is shown.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":134,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_id, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_id, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_write_batch_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, table_id, job, instance) (rate(state_store_write_batch_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(state_store_write_batch_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, table_id, job, instance) (rate(state_store_write_batch_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":135,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_mem_table_spill_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mem table spill table id - {{table_id}} @ {{instance}}","metric":"","query":"sum(irate(state_store_mem_table_spill_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Mem Table Spill Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":136,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Sync Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":137,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_event_handler_pending_event{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"sum(state_store_event_handler_pending_event{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handler pending event number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":138,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 {{event_type}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 {{event_type}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax {{event_type}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax finished_task_wait_poll {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handle latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Write)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":12},"height":null,"hideTimeOverride":false,"id":139,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":140,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","query":"sum(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size(KB) of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":141,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","query":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Size(KB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The of bytes that have been written by commit epoch per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":142,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_commit_write_throughput{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{table_id}}","metric":"","query":"sum(rate(storage_commit_write_throughput{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Flush Bytes by Table","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":143,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result!='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","query":"sum(storage_level_compact_frequency{result!='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Failure Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":144,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","query":"sum(storage_level_compact_frequency{result='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Success Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have been skipped.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":145,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_skip_compact_frequency{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (level, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{level}}-{{type}}","metric":"","query":"sum(rate(storage_skip_compact_frequency{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (level, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Skip Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg l0 select_level_count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":146,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_l0_compact_level_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_l0_compact_level_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","query":"sum by(le, group, type)(irate(storage_l0_compact_level_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_l0_compact_level_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task L0 Select Level Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg file count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":147,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_compact_task_file_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_compact_task_file_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","query":"sum by(le, group, type)(irate(storage_compact_task_file_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_compact_task_file_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task File Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The distribution of the compact task size triggered, including p90 and max. and categorize it according to different cg, levels and task types.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":148,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - cg{{group}}@{{type}}","metric":"","query":"histogram_quantile(0.9, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - cg{{group}}@{{type}}","metric":"","query":"histogram_quantile(1.0, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task Size Distribution","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that are running.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":149,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_num{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_count - {{job}} @ {{instance}}","metric":"","query":"avg(storage_compact_task_pending_num{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_parallelism{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_pending_parallelism - {{job}} @ {{instance}}","metric":"","query":"avg(storage_compact_task_pending_parallelism{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Running Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compact-task: The total time have been spent on compaction.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":150,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p50 - {{job}}","metric":"","query":"histogram_quantile(0.5, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(compute_refill_cache_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compute_apply_version_duration_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(compute_refill_cache_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(compactor_compact_task_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(compactor_compact_task_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task avg","metric":"","query":"sum by(le)(rate(compactor_compact_task_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(compactor_compact_task_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(state_store_compact_sst_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(state_store_compact_sst_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range avg","metric":"","query":"sum by(le)(rate(state_store_compact_sst_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(state_store_compact_sst_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"KBs read from next level during history compactions to next level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":151,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job) + sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","query":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job) + sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","query":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","query":"sum(rate(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_fast_compact_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fast compact - {{job}}","metric":"","query":"sum(rate(compactor_fast_compact_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Write refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":152,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","query":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","query":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Bytes(GiB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Write amplification is the amount of bytes written to the remote storage by compaction for each one byte of flushed SSTable data. Write amplification is by definition higher than 1.0 because we write each piece of data to L0, and then write it again to an SSTable, and then compaction may read this piece of data and write it to a new SSTable, that's another write.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":153,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) / sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write amplification","metric":"","query":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) / sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Amplification","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables that is being compacted at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":154,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","query":"storage_level_compact_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"num of compact_task","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":155,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_task_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task}}","metric":"","query":"storage_level_compact_task_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":156,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","query":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","query":"sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","query":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"KBs Read/Write by Level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":157,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_write_sstn{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","query":"sum(irate(storage_level_compact_write_sstn{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","query":"sum(irate(storage_level_compact_read_sstn_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","query":"sum(irate(storage_level_compact_read_sstn_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Count of SSTs Read/Write by level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_bloom_filter, for observing bloom_filter size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":158,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_meta - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_file_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_file_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_file - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_file_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_file_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_avg_key_size, for observing sstable_avg_key_size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":159,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_key_size - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_value_size - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Item Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg count gotten from sstable_distinct_epoch_count, for observing sstable_distinct_epoch_count","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":160,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_epoch_count - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Stat","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total time of operations which read from remote storage when enable prefetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":161,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Remote Read Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":162,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_iter_scan_key_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{type}} @ {{instance}} ","metric":"","query":"sum(rate(compactor_iter_scan_key_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Iter keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"bytes of Lsm tree needed to reach balance","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":163,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_pending_bytes{job=~\"$job\",instance=~\"$node\"}) by (instance, group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact pending bytes - {{group}} @ {{instance}} ","metric":"","query":"sum(storage_compact_pending_bytes{job=~\"$job\",instance=~\"$node\"}) by (instance, group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Compact Pending Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compression ratio of each level of the lsm tree","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":164,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_level_compression_ratio{job=~\"$job\",instance=~\"$node\"}) by (instance, group, level, algorithm)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lsm compression ratio - cg{{group}} @ L{{level}} - {{algorithm}} {{instance}}","metric":"","query":"sum(storage_compact_level_compression_ratio{job=~\"$job\",instance=~\"$node\"}) by (instance, group, level, algorithm)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Level Compression Ratio","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Compaction","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":13},"height":null,"hideTimeOverride":false,"id":165,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":166,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":167,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, type, job, instance)(rate(object_store_operation_latency_sum{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(object_store_operation_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} avg - {{job}} @ {{instance}}","metric":"","query":"sum by(le, type, job, instance)(rate(object_store_operation_latency_sum{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(object_store_operation_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":168,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type!~'streaming_upload_write_bytes|streaming_read_read_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_operation_latency_count{type!~'streaming_upload_write_bytes|streaming_read_read_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'upload|delete',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-write - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_operation_latency_count{type=~'upload|delete',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'read|readv|list|metadata',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-read - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_operation_latency_count{type=~'read|readv|list|metadata',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":169,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":170,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Failure Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":171,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_request_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_request_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Retry Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"There are two types of operations: 1. GET, SELECT, and DELETE, they cost 0.0004 USD per 1000 requests. 2. PUT, COPY, POST, LIST, they cost 0.005 USD per 1000 requests.Reading from S3 across different regions impose extra cost. This metric assumes 0.01 USD per 1GB data transfer. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":172,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}) * 0.01 / 1000 / 1000 / 1000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"(Cross Region) Data Transfer Cost","metric":"","query":"sum(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}) * 0.01 / 1000 / 1000 / 1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'read|streaming_read_start|streaming_read_init',job=~\"$job\",instance=~\"$node\"}) * 0.0004 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GET, SELECT, and all other Requests Cost","metric":"","query":"sum(object_store_operation_latency_count{type=~'read|streaming_read_start|streaming_read_init',job=~\"$job\",instance=~\"$node\"}) * 0.0004 / 1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'upload|streaming_upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|list',job=~\"$job\",instance=~\"$node\"}) * 0.005 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"PUT, COPY, POST, LIST Requests Cost","metric":"","query":"sum(object_store_operation_latency_count{type=~'upload|streaming_upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|list',job=~\"$job\",instance=~\"$node\"}) * 0.005 / 1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Realtime)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric uses the total size of data in S3 at this second to derive the cost of storing data for a whole month. The price is 0.023 USD per GB. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":173,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance) * 0.023 / 1000 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Monthly Storage Cost","metric":"","query":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance) * 0.023 / 1000 / 1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Monthly)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Object Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":14},"height":null,"hideTimeOverride":false,"id":174,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":175,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_hybrid_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_hybrid_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hybrid Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":176,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hybrid Cache Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":177,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_hybrid_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - hybrid - hit ratio @ {{instance}}","metric":"","query":"sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_hybrid_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hybrid Cache Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":178,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_memory_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - memory - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_memory_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":179,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(foyer_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - memory - size @ {{instance}}","metric":"","query":"sum(foyer_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Cache Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":180,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_memory_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - memory - hit ratio @ {{instance}}","metric":"","query":"sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_memory_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Cache Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":181,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":182,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_inner_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_inner_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Inner Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":183,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":184,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Inner Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":185,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_storage_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - storage - hit ratio @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_storage_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":186,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(foyer_storage_region{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance) * on(name, instance) group_left() avg(foyer_storage_region_size_bytes{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - {{type}} region - size @ {{instance}}","metric":"","query":"sum(foyer_storage_region{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance) * on(name, instance) group_left() avg(foyer_storage_region_size_bytes{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Region Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":187,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_disk_io_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_disk_io_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Disk Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":188,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Disk Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":189,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_disk_io_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_disk_io_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Disk Op Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":190,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=~\"meta|data\",op!~\"filtered|ignored\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=~\"meta|data\",op!~\"filtered|ignored\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":191,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache - {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Data Refill Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":192,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":193,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(refill_queue_total) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"refill queue length @ {{instance}}","metric":"","query":"sum(refill_queue_total) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Queue Length","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":194,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"parent_meta\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":195,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"parent_meta\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup hit ratio @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"parent_meta\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":196,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"unit_inheritance\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":197,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"unit_inheritance\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance ratio @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"unit_inheritance\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":198,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"block\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":199,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",op=\"success\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / sum(rate(refill_total{type=\"block\",op=\"unfiltered\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill ratio @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"block\",op=\"success\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / sum(rate(refill_total{type=\"block\",op=\"unfiltered\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Item numbers of the recent filter.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":96},"height":null,"hideTimeOverride":false,"id":200,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(recent_filter_items{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"items @ {{instance}}","metric":"","query":"sum(recent_filter_items{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recent Filter Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":104},"height":null,"hideTimeOverride":false,"id":201,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recent_filter_ops{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recent filter {{op}} @ {{instance}}","metric":"","query":"sum(rate(recent_filter_ops{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recent Filter Ops","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Tiered Cache","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":15},"height":null,"hideTimeOverride":false,"id":202,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":203,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p50 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","query":"histogram_quantile(0.5, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p99 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","query":"histogram_quantile(0.99, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time pmax - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","query":"histogram_quantile(1.0, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lock Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":204,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p50 - {{method}}","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p99 - {{method}}","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time pmax - {{method}}","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Real Process Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":205,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version size","metric":"","query":"storage_version_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":206,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"current version id","metric":"","query":"storage_current_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"checkpoint version id","metric":"","query":"storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned version id","metric":"","query":"storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_safepoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min safepoint version id","metric":"","query":"storage_min_safepoint_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Id","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":207,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"max committed epoch","metric":"","query":"storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_safe_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"safe epoch","metric":"","query":"storage_safe_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned epoch","metric":"","query":"storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":208,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","query":"storage_version_stats{metric='total_key_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_value_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","query":"storage_version_stats{metric='total_value_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":209,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","query":"storage_materialized_view_stats{metric='materialized_view_total_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":210,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_count',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","query":"storage_version_stats{metric='total_key_count',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table KV Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\nObjects are classified into 3 groups:\n- not referenced by versions: these object are being deleted from object store.\n- referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n- referenced by current version: these objects are in the latest version.\n\nAdditionally, a metric on all objects (including dangling ones) is updated with low-frequency. The metric is updated right before full GC. So subsequent full GC may reduce the actual value significantly, without updating the metric.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":211,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects (including dangling ones)","metric":"","query":"storage_total_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Refer to `Object Total Number` panel for classification of objects.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":212,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects, including dangling ones","metric":"","query":"storage_total_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of hummock version delta log","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":213,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_delta_log_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"delta log total number","metric":"","query":"storage_delta_log_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Delta Log Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"hummock version checkpoint latency","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":214,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(storage_version_checkpoint_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(storage_version_checkpoint_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_avg","metric":"","query":"rate(storage_version_checkpoint_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(storage_version_checkpoint_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Checkpoint Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When certain per compaction group threshold is exceeded (e.g. number of level 0 sub-level in LSMtree), write op to that compaction group is stopped temporarily. Check log for detail reason of write stop.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":215,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compaction_group_{{compaction_group_id}}","metric":"","query":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Stop Compaction Groups","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of attempts to trigger full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":216,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_trigger_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_trigger_count","metric":"","query":"storage_full_gc_trigger_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Trigger Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"the object id watermark used in last full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":217,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_last_object_id_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_last_object_id_watermark","metric":"","query":"storage_full_gc_last_object_id_watermark{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Last Watermark","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":218,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Event Loop Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The times of move_state_table occurs","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":219,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_move_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"move table cg{{group}}","metric":"","query":"sum(storage_move_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}) by (group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Move State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of state_tables in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":220,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"state table cg{{group}}","metric":"","query":"sum(irate(storage_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of branched_sst in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":221,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_branched_sst_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"branched sst cg{{group}}","metric":"","query":"sum(irate(storage_branched_sst_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Branched SST Count","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":222,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total backup job count since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":223,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"backup_job_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"job count","metric":"","query":"backup_job_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Latency of backup jobs since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":224,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p50 - {{state}}","metric":"","query":"histogram_quantile(0.5, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p99 - {{state}}","metric":"","query":"histogram_quantile(0.99, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time pmax - {{state}}","metric":"","query":"histogram_quantile(1.0, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Process Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Backup Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":17},"height":null,"hideTimeOverride":false,"id":225,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":226,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":227,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Drop latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":228,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetCatalog latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Catalog Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":229,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":230,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"AddWorkerNode latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":231,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ListAllNodes latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Cluster Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":19},"height":null,"hideTimeOverride":false,"id":232,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":233,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CreateMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":234,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"DropMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":235,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Flush latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Stream Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":20},"height":null,"hideTimeOverride":false,"id":236,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":237,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinVersionBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":238,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinSnapshotBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":239,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ReportCompactionTasks latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":240,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetNewSstIds latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":21},"height":null,"hideTimeOverride":false,"id":241,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":242,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_counts - {{instance}}","metric":"","query":"sum(irate(state_store_report_compaction_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":243,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_version_before_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_version_before_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_avg","metric":"","query":"sum(irate(state_store_unpin_version_before_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_version_before_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"version_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":244,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latencyp90 - {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_avg","metric":"","query":"sum(irate(state_store_pin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_avg","metric":"","query":"sum(irate(state_store_unpin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_unpin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":245,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_counts - {{instance}}","metric":"","query":"sum(irate(state_store_pin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_counts - {{instance}}","metric":"","query":"sum(irate(state_store_unpin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":246,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_get_new_sst_ids_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_avg","metric":"","query":"sum(irate(state_store_get_new_sst_ids_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_get_new_sst_ids_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":247,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_counts - {{instance}}","metric":"","query":"sum(irate(state_store_get_new_sst_ids_latency_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":248,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_avg","metric":"","query":"sum(irate(state_store_report_compaction_task_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC: Hummock Meta Client","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":22},"height":null,"hideTimeOverride":false,"id":249,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":250,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":251,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":252,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":253,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","query":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Running Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":254,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","query":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Rejected queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":255,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","query":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Completed Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":256,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":257,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Frontend","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":23},"height":null,"hideTimeOverride":false,"id":258,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":259,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(lru_runtime_loop_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"rate(lru_runtime_loop_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager loop count per sec","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":260,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_eviction_policy{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"lru_eviction_policy{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager eviction policy","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":261,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_latest_sequence{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"lru_latest_sequence{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_watermark_sequence{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"lru_watermark_sequence{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager sequence","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":262,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":263,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_active_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":264,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_resident_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_resident_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The resident memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":265,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_metadata_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_metadata_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The metadata memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":266,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jvm_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":267,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jvm_active_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":268,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"} - on() group_right() lru_evicted_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","query":"lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"} - on() group_right() lru_evicted_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager diff between current watermark and evicted watermark time (ms) for actors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":269,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":270,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","query":"rate(source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":271,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","query":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":25},"height":null,"hideTimeOverride":false,"id":272,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":273,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{connector}} {{sink_id}}","metric":"","query":"histogram_quantile(0.5, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{connector}} {{sink_id}}","metric":"","query":"histogram_quantile(0.99, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{connector}} {{sink_id}}","metric":"","query":"histogram_quantile(1.0, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, connector, sink_id)(rate(sink_commit_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(sink_commit_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{connector}} @ {{sink_id}}","metric":"","query":"sum by(le, connector, sink_id)(rate(sink_commit_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(sink_commit_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":274,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest write epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest read epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_min_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Kv log store uncomsuned min epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_min_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Read/Write Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":275,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(max(log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"(max(log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":276,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(log_store_reader_wait_new_future_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id, executor_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Backpressure @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"avg(rate(log_store_reader_wait_new_future_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id, executor_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Backpressure Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":277,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min((max(log_store_first_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume persistent log lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"clamp_min((max(log_store_first_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000, 0)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Persistent Log Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":278,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","query":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":279,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","query":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":280,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","query":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":281,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","query":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":282,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_read_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":283,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_read_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":284,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_write_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":285,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_write_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":286,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_item_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Unconsumed item count @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_item_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_row_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Unconsumed row count @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_row_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_epoch_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Unconsumed epoch count @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_epoch_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Buffer State","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":287,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_rewind_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_rewind_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Rewind Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":288,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(kv_log_store_rewind_delay_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor_id, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"histogram_quantile(1.0, sum(rate(kv_log_store_rewind_delay_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor_id, connector, sink_id))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rewind delay (second)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total size of chunks buffered in a barrier","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":289,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_sink_chunk_buffer_size{job=~\"$job\",instance=~\"$node\"}) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}} - actor {{actor_id}}","metric":"","query":"sum(stream_sink_chunk_buffer_size{job=~\"$job\",instance=~\"$node\"}) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Chunk Buffer Size","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":290,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka high watermark by source and partition and source latest message by partition, source and actor","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":291,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"high watermark: source={{source_id}} partition={{partition}}","metric":"","query":"source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_latest_message_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest msg: source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","query":"source_latest_message_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka high watermark and source latest message","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current number of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":292,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_msg_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current total size of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":293,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_msg_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Size in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages transmitted (produced) to Kafka brokers","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":294,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_tx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_tx_msgs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Produced Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages consumed, not including ignored messages (due to offset, etc), from Kafka brokers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":295,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_rx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_rx_msgs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Received Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages awaiting transmission to broker","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":296,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_outbuf_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_outbuf_msg_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count Pending to Transmit (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages in-flight to broker awaiting response","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":297,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_waitresp_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_waitresp_msg_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inflight Message Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of transmission errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":298,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_tx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_tx_errs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Transmitting (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of receive errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":299,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_rx_errs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Receiving (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of requests timed out","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":300,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_req_timeouts{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_req_timeouts{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Timeout Request Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker latency / round-trip time in milli seconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":301,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_avg{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p75{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p90{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"RTT (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker throttling time in milliseconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":302,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_avg{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p75{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p90{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Throttle Time (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Age of metadata from broker for this topic (milliseconds)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":303,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_metadata_age{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}","metric":"","query":"rdkafka_topic_metadata_age{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Metadata_age Age","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch sizes in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":304,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_avg{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p75{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p90{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p99_99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_out_of_range{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch message counts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":null,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_avg{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p75{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p90{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p99_99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_out_of_range{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Messages","transformations":[],"transparent":false,"type":"timeseries"}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages ready to be produced in transmit queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":305,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_xmit_msgq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_xmit_msgq_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message to be Transmitted","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of pre-fetched messages in fetch queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":306,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_fetchq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_fetchq_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message in pre fetch queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Next offset to fetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":307,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_next_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_next_offset{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Next offset to fetch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Last committed offset","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":308,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_committed_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_committed_offset{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Committed Offset","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Kafka Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":27},"height":null,"hideTimeOverride":false,"id":309,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":310,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Network throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":311,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"S3 throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":312,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"gRPC throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":313,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_io_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","query":"sum(irate(connection_io_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} grpc {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","query":"sum(rate(connection_io_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","query":"sum(rate(connection_io_err_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"IO error rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":314,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","query":"sum(connection_count{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","query":"sum(connection_count{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}) by (job, instance, connection_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Existing connection count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":315,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","query":"sum(irate(connection_create_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","query":"sum(irate(connection_create_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":316,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","query":"sum(irate(connection_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","query":"sum(irate(connection_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection err rate","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network connection","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":28},"height":null,"hideTimeOverride":false,"id":317,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"iceberg write qps","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":318,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_write_qps{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_write_qps{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Qps Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":319,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{sink_id}}","metric":"","query":"histogram_quantile(0.5, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{sink_id}}","metric":"","query":"histogram_quantile(0.99, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{sink_id}}","metric":"","query":"histogram_quantile(1.0, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, sink_id)(rate(iceberg_write_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(iceberg_write_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg @ {{sink_id}}","metric":"","query":"sum by(le, sink_id)(rate(iceberg_write_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(iceberg_write_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Latency Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":320,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_rolling_unfushed_data_file{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_rolling_unfushed_data_file{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg rolling unfushed data file","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":321,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_position_delete_cache_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_position_delete_cache_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg position delete cache num","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":322,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_partition_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_partition_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg partition num","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Iceberg Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":29},"height":null,"hideTimeOverride":false,"id":323,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":324,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{instance}}","metric":"","query":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{instance}}","metric":"","query":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","query":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","query":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Calls Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":325,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_input_chunk_rows_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_input_chunk_rows_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_input_chunk_rows_avg - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(irate(udf_input_chunk_rows_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_input_chunk_rows_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Input Chunk Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":326,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.50, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.50, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg - {{instance}}","metric":"","query":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, link, name, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","query":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, link, name, fragment_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":327,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{instance}}","metric":"","query":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":328,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{instance}}","metric":"","query":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (bytes)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Currently only embedded JS UDF supports this. Others will always show 0.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":329,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_memory_usage - {{instance}}","metric":"","query":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_memory_usage - {{name}} {{fragment_id}}","metric":"","query":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Memory Usage (bytes)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Defined Function","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(table_info, table_id)","description":"Reporting table id of the metric","hide":0,"includeAll":true,"label":"Table","multi":true,"name":"table","options":[],"query":{"query":"label_values(table_info, table_id)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"nowDelay":null,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dev_dashboard","uid":"Ecy3uV1nz","version":0} diff --git a/docker/dashboards/risingwave-user-dashboard.json b/docker/dashboards/risingwave-user-dashboard.json index 5fbc9681be606..037679704e38f 100644 --- a/docker/dashboards/risingwave-user-dashboard.json +++ b/docker/dashboards/risingwave-user-dashboard.json @@ -1 +1 @@ -{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dashboard","editable":true,"gnetId":null,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":false,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Overview","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":5,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":2},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":10},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":9,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Alerts in the system group by type:\n - Too Many Barriers: there are too many uncommitted barriers generated. This means the streaming graph is stuck or under heavy load. Check 'Barrier Latency' panel.\n - Recovery Triggered: cluster recovery is triggered. Check 'Errors by Type' / 'Node Count' panels.\n - Lagging Version: the checkpointed or pinned version id is lagging behind the current version id. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Epoch: the pinned or safe epoch is lagging behind the current max committed epoch. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Compaction: there are too many files in L0. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Lagging Vacuum: there are too many stale files waiting to be cleaned. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Abnormal Meta Cache Memory: the meta cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Block Cache Memory: the block cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Uploading Memory Usage: uploading memory is more than 70 percent of the expected, and is about to spill.\n - Write Stall: Compaction cannot keep up. Stall foreground write.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":18},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Too Many Barriers","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > bool 0 + sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recovery Triggered","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100) + ((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Version","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}) >= bool 6553600000 unless + storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"} == 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Epoch","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(label_replace(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}, 'L0', 'L0', 'level_index', '.*_L0') unless storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (L0) >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Compaction","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Vacuum","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_meta_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Meta Cache Memory","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_block_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Block Cache Memory","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_uploading_memory_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 0.7","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Uploading Memory Usage","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"} > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Write Stall","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Alerts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors in the system group by type","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"} == 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source error: source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote storage error {{type}}: {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Errors","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":26},"height":null,"hideTimeOverride":false,"id":12,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Local mode","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distributed mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Query QPS","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":34},"height":null,"hideTimeOverride":false,"id":13,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions in frontend nodes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":34},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":42},"height":null,"hideTimeOverride":false,"id":15,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":16,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of CPU cores per RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Core Number","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"CPU","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":43},"height":null,"hideTimeOverride":false,"id":18,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":19,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Total)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(actor_memory_usage[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"streaming actor - {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage meta cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage block cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage write buffer - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized_view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Detailed)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Executor cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - cache miss - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - total lookups - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - cache miss - table_id {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - total lookups - table_id {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - cache hit count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - total cache count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialize executor cache miss ratio - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"memory cache - {{table_id}} @ {{type}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage bloom filter statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_check_bloom_filter_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter total - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_positive_but_non_exist_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Bloom Filer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage file cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_miss{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache miss @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage File Cache","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":44},"height":null,"hideTimeOverride":false,"id":27,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Send/Recv throughput per node for streaming exchange","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":28,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Send @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recv @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Streming Remote Exchange (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput per node","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Exchange Recv (Rows/s)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":45},"height":null,"hideTimeOverride":false,"id":31,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":32,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The storage size of each materialized view","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Compaction refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Compaction - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":36,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Size statistics for checkpoint","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Size","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":46},"height":null,"hideTimeOverride":false,"id":38,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":39,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id)(rate(partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":41,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized executor actor per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{executor_identity=~\".*MaterializeExecutor.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(actor_id) group_left(materialized_view_id, table_name) (group(table_info{table_type=~\"MATERIALIZED_VIEW\",job=~\"$job\",instance=~\"$node\"}) by (actor_id, materialized_view_id, table_name))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{table_name}} table_id {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill operator used by MV on MV","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Read Snapshot - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Upstream - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":44,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":47},"height":null,"hideTimeOverride":false,"id":45,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":46,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Running query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rejected query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Completed query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Distributed Execution Mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Local Execution Mode","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":51,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":52,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dashboard","uid":"Fcy3uV1nz","version":0} +{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dashboard","editable":true,"gnetId":null,"graphTooltip":0,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Actor count per compute node","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Count (Group By Compute Node)","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":false,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":5,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Overview","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":2},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","query":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":10},"height":null,"hideTimeOverride":false,"id":9,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","query":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","query":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Alerts in the system group by type:\n - Too Many Barriers: there are too many uncommitted barriers generated. This means the streaming graph is stuck or under heavy load. Check 'Barrier Latency' panel.\n - Recovery Triggered: cluster recovery is triggered. Check 'Errors by Type' / 'Node Count' panels.\n - Lagging Version: the checkpointed or pinned version id is lagging behind the current version id. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Epoch: the pinned or safe epoch is lagging behind the current max committed epoch. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Compaction: there are too many files in L0. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Lagging Vacuum: there are too many stale files waiting to be cleaned. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Abnormal Meta Cache Memory: the meta cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Block Cache Memory: the block cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Uploading Memory Usage: uploading memory is more than 70 percent of the expected, and is about to spill.\n - Write Stall: Compaction cannot keep up. Stall foreground write.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":18},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Too Many Barriers","metric":"","query":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"} >= bool 200","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > bool 0 + sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recovery Triggered","metric":"","query":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > bool 0 + sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) > bool 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100) + ((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Version","metric":"","query":"((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100) + ((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}) >= bool 6553600000 unless + storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"} == 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Epoch","metric":"","query":"((storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}) >= bool 6553600000 unless + storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"} == 0)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(label_replace(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}, 'L0', 'L0', 'level_index', '.*_L0') unless storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (L0) >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Compaction","metric":"","query":"sum(label_replace(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}, 'L0', 'L0', 'level_index', '.*_L0') unless storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (L0) >= bool 200","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Vacuum","metric":"","query":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"} >= bool 200","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_meta_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Meta Cache Memory","metric":"","query":"state_store_meta_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_block_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Block Cache Memory","metric":"","query":"state_store_block_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_uploading_memory_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 0.7","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Uploading Memory Usage","metric":"","query":"state_store_uploading_memory_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 0.7","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"} > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Write Stall","metric":"","query":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"} > bool 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Alerts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors in the system group by type","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":12,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","query":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"} == 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source error: source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","query":"source_status_is_up{job=~\"$job\",instance=~\"$node\"} == 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote storage error {{type}}: {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Errors","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":26},"height":null,"hideTimeOverride":false,"id":13,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Local mode","metric":"","query":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distributed mode","metric":"","query":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Query QPS","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":34},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","query":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions in frontend nodes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":34},"height":null,"hideTimeOverride":false,"id":15,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":42},"height":null,"hideTimeOverride":false,"id":16,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of CPU cores per RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":18,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Core Number","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"CPU","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":43},"height":null,"hideTimeOverride":false,"id":19,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage @ {{instance}}","metric":"","query":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Total)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(actor_memory_usage[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"streaming actor - {{actor_id}}","metric":"","query":"rate(actor_memory_usage[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage meta cache - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage block cache - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage write buffer - {{job}} @ {{instance}}","metric":"","query":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized_view {{materialized_view_id}}","metric":"","query":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info) by (materialized_view_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Detailed)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Executor cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - cache miss - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - total lookups - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - cache miss - table_id {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - total lookups - table_id {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - cache hit count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","query":"rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - total cache count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","query":"rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id)) >=0 ","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","query":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id)) >=0 ","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0 ","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0 ","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialize executor cache miss ratio - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","query":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"memory cache - {{table_id}} @ {{type}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage bloom filter statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_check_bloom_filter_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter total - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_read_req_check_bloom_filter_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_positive_but_non_exist_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_read_req_positive_but_non_exist_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Bloom Filer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage file cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":27,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache {{op}} @ {{instance}}","metric":"","query":"sum(rate(file_cache_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_miss{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache miss @ {{instance}}","metric":"","query":"sum(rate(file_cache_miss{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage File Cache","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":44},"height":null,"hideTimeOverride":false,"id":28,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Send/Recv throughput per node for streaming exchange","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Send @ {{instance}}","metric":"","query":"sum(rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recv @ {{instance}}","metric":"","query":"sum(rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Streming Remote Exchange (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput per node","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{instance}}","metric":"","query":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{instance}}","metric":"","query":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":31,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","query":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Exchange Recv (Rows/s)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":45},"height":null,"hideTimeOverride":false,"id":32,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The storage size of each materialized view","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","query":"storage_materialized_view_stats{metric='materialized_view_total_size',job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Compaction refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":36,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Compaction - {{job}}","metric":"","query":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush - {{job}}","metric":"","query":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","query":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","query":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Size statistics for checkpoint","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":38,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}}","metric":"","query":"sum by(le, job) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Size","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":46},"height":null,"hideTimeOverride":false,"id":39,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":41,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id)(rate(partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}}","metric":"","query":"(sum by (source_id)(rate(partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized executor actor per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{executor_identity=~\".*MaterializeExecutor.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(actor_id) group_left(materialized_view_id, table_name) (group(table_info{table_type=~\"MATERIALIZED_VIEW\",job=~\"$job\",instance=~\"$node\"}) by (actor_id, materialized_view_id, table_name))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{table_name}} table_id {{materialized_view_id}}","metric":"","query":"sum(rate(stream_executor_row_count{executor_identity=~\".*MaterializeExecutor.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(actor_id) group_left(materialized_view_id, table_name) (group(table_info{table_type=~\"MATERIALIZED_VIEW\",job=~\"$job\",instance=~\"$node\"}) by (actor_id, materialized_view_id, table_name))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill operator used by MV on MV","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":44,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Read Snapshot - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_snapshot_read_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Upstream - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_upstream_output_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":45,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","query":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":47},"height":null,"hideTimeOverride":false,"id":46,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","query":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Running query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","query":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rejected query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","query":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Completed query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Distributed Execution Mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":51,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Local Execution Mode","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":52,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","query":"rate(connector_source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":54,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","query":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"nowDelay":null,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dashboard","uid":"Fcy3uV1nz","version":0} diff --git a/docker/docker-compose-distributed-etcd.yml b/docker/docker-compose-distributed-etcd.yml new file mode 100644 index 0000000000000..16382a54a2b73 --- /dev/null +++ b/docker/docker-compose-distributed-etcd.yml @@ -0,0 +1,380 @@ +--- +version: "3" +x-image: &image + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} +services: + compactor-0: + <<: *image + command: + - compactor-node + - "--listen-addr" + - "0.0.0.0:6660" + - "--advertise-addr" + - "compactor-0:6660" + - "--prometheus-listener-addr" + - "0.0.0.0:1260" + - "--meta-address" + - "http://meta-node-0:5690" + - "--config-path" + - /risingwave.toml + expose: + - "6660" + - "1260" + ports: [ ] + depends_on: + - meta-node-0 + # - minio-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: compactor-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/6660; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + compute-node-0: + <<: *image + command: + - compute-node + - "--listen-addr" + - "0.0.0.0:5688" + - "--advertise-addr" + - "compute-node-0:5688" + - "--prometheus-listener-addr" + - "0.0.0.0:1222" + - "--meta-address" + - "http://meta-node-0:5690" + - "--config-path" + - /risingwave.toml + expose: + - "5688" + - "1222" + ports: [ ] + depends_on: + - meta-node-0 + # - minio-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: compute-node-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5688; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 26G + reservations: + memory: 26G + etcd-0: + image: "quay.io/coreos/etcd:v3.5.10" + command: + - /usr/local/bin/etcd + - "--listen-client-urls" + - "http://0.0.0.0:2388" + - "--advertise-client-urls" + - "http://etcd-0:2388" + - "--listen-peer-urls" + - "http://0.0.0.0:2389" + - "--initial-advertise-peer-urls" + - "http://etcd-0:2389" + - "--listen-metrics-urls" + - "http://0.0.0.0:2379" + - "--name" + - risedev-meta + - "--max-txn-ops" + - "999999" + - "--max-request-bytes" + - "10485760" + - "--auto-compaction-mode" + - periodic + - "--auto-compaction-retention" + - 1m + - "--snapshot-count" + - "10000" + - "--data-dir" + - /etcd-data + expose: + - "2388" + ports: + - "2388:2388" + - "2389:2389" + depends_on: [ ] + volumes: + - "etcd-0:/etcd-data" + environment: { } + container_name: etcd-0 + healthcheck: + test: + - CMD + - etcdctl + - --endpoints=http://localhost:2388 + - endpoint + - health + interval: 1s + timeout: 5s + retries: 5 + restart: always + frontend-node-0: + <<: *image + command: + - frontend-node + - "--listen-addr" + - "0.0.0.0:4566" + - "--meta-addr" + - "http://meta-node-0:5690" + - "--advertise-addr" + - "frontend-node-0:4566" + - "--config-path" + - /risingwave.toml + - "--prometheus-listener-addr" + - "0.0.0.0:2222" + expose: + - "4566" + ports: + - "4566:4566" + depends_on: + - meta-node-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: frontend-node-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/4566; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + grafana-0: + image: "grafana/grafana-oss:latest" + command: [ ] + expose: + - "3001" + ports: + - "3001:3001" + depends_on: [ ] + volumes: + - "grafana-0:/var/lib/grafana" + - "./grafana.ini:/etc/grafana/grafana.ini" + - "./grafana-risedev-datasource.yml:/etc/grafana/provisioning/datasources/grafana-risedev-datasource.yml" + - "./grafana-risedev-dashboard.yml:/etc/grafana/provisioning/dashboards/grafana-risedev-dashboard.yml" + - "./dashboards:/dashboards" + environment: { } + container_name: grafana-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/3001; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + meta-node-0: + <<: *image + command: + - meta-node + - "--listen-addr" + - "0.0.0.0:5690" + - "--advertise-addr" + - "meta-node-0:5690" + - "--dashboard-host" + - "0.0.0.0:5691" + - "--prometheus-host" + - "0.0.0.0:1250" + - "--prometheus-endpoint" + - "http://prometheus-0:9500" + - "--backend" + - etcd + - "--etcd-endpoints" + - "etcd-0:2388" + - "--state-store" + - "hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001" + - "--data-directory" + - "hummock_001" + - "--config-path" + - /risingwave.toml + expose: + - "5690" + - "1250" + - "5691" + ports: + - "5690:5690" + - "5691:5691" + depends_on: + - "etcd-0" + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} + container_name: meta-node-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5690; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + minio-0: + image: "quay.io/minio/minio:latest" + command: + - server + - "--address" + - "0.0.0.0:9301" + - "--console-address" + - "0.0.0.0:9400" + - /data + expose: + - "9301" + - "9400" + ports: + - "9301:9301" + - "9400:9400" + depends_on: [ ] + volumes: + - "minio-0:/data" + entrypoint: " + + /bin/sh -c ' + + set -e + + mkdir -p \"/data/hummock001\" + + /usr/bin/docker-entrypoint.sh \"$$0\" \"$$@\" + + '" + environment: + MINIO_CI_CD: "1" + MINIO_PROMETHEUS_AUTH_TYPE: public + MINIO_PROMETHEUS_URL: "http://prometheus-0:9500" + MINIO_ROOT_PASSWORD: hummockadmin + MINIO_ROOT_USER: hummockadmin + MINIO_DOMAIN: "minio-0" + container_name: minio-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/9301; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + prometheus-0: + image: "prom/prometheus:latest" + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.console.libraries=/usr/share/prometheus/console_libraries" + - "--web.console.templates=/usr/share/prometheus/consoles" + - "--web.listen-address=0.0.0.0:9500" + - "--storage.tsdb.retention.time=30d" + expose: + - "9500" + ports: + - "9500:9500" + depends_on: [ ] + volumes: + - "prometheus-0:/prometheus" + - "./prometheus.yaml:/etc/prometheus/prometheus.yml" + environment: { } + container_name: prometheus-0 + healthcheck: + test: + - CMD-SHELL + - sh -c 'printf "GET /-/healthy HTTP/1.0\n\n" | nc localhost 9500; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + message_queue: + image: "docker.vectorized.io/vectorized/redpanda:latest" + command: + - redpanda + - start + - "--smp" + - "1" + - "--reserve-memory" + - 0M + - "--memory" + - 4G + - "--overprovisioned" + - "--node-id" + - "0" + - "--check=false" + - "--kafka-addr" + - "PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092" + - "--advertise-kafka-addr" + - "PLAINTEXT://message_queue:29092,OUTSIDE://localhost:9092" + expose: + - "29092" + - "9092" + - "9644" + ports: + - "29092:29092" + - "9092:9092" + - "9644:9644" + - "8081:8081" + depends_on: [ ] + volumes: + - "message_queue:/var/lib/redpanda/data" + environment: { } + container_name: message_queue + healthcheck: + test: curl -f localhost:9644/v1/status/ready + interval: 1s + timeout: 5s + retries: 5 + restart: always +volumes: + etcd-0: + external: false + grafana-0: + external: false + minio-0: + external: false + prometheus-0: + external: false + message_queue: + external: false diff --git a/docker/docker-compose-distributed.yml b/docker/docker-compose-distributed.yml index 55cb1cbcffe3c..843d15c39cfde 100644 --- a/docker/docker-compose-distributed.yml +++ b/docker/docker-compose-distributed.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: compactor-0: <<: *image @@ -20,7 +19,7 @@ services: expose: - "6660" - "1260" - ports: [] + ports: [ ] depends_on: - meta-node-0 # - minio-0 @@ -62,7 +61,7 @@ services: expose: - "5688" - "1222" - ports: [] + ports: [ ] depends_on: - meta-node-0 # - minio-0 @@ -87,52 +86,22 @@ services: memory: 26G reservations: memory: 26G - etcd-0: - image: "quay.io/coreos/etcd:v3.5.10" - command: - - /usr/local/bin/etcd - - "--listen-client-urls" - - "http://0.0.0.0:2388" - - "--advertise-client-urls" - - "http://etcd-0:2388" - - "--listen-peer-urls" - - "http://0.0.0.0:2389" - - "--initial-advertise-peer-urls" - - "http://etcd-0:2389" - - "--listen-metrics-urls" - - "http://0.0.0.0:2379" - - "--name" - - risedev-meta - - "--max-txn-ops" - - "999999" - - "--max-request-bytes" - - "10485760" - - "--auto-compaction-mode" - - periodic - - "--auto-compaction-retention" - - 1m - - "--snapshot-count" - - "10000" - - "--data-dir" - - /etcd-data + postgres-0: + image: "postgres:15-alpine" + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + - POSTGRES_USER=postgres + - POSTGRES_DB=metadata + - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C expose: - - "2388" + - "5432" ports: - - "2388:2388" - - "2389:2389" - depends_on: [] + - "8432:5432" volumes: - - "etcd-0:/etcd-data" - environment: {} - container_name: etcd-0 + - "postgres-0:/var/lib/postgresql/data" healthcheck: - test: - - CMD - - etcdctl - - --endpoints=http://localhost:2388 - - endpoint - - health - interval: 1s + test: [ "CMD-SHELL", "pg_isready -U postgres" ] + interval: 2s timeout: 5s retries: 5 restart: always @@ -179,12 +148,12 @@ services: memory: 1G grafana-0: image: "grafana/grafana-oss:latest" - command: [] + command: [ ] expose: - "3001" ports: - "3001:3001" - depends_on: [] + depends_on: [ ] volumes: - "grafana-0:/var/lib/grafana" - "./grafana.ini:/etc/grafana/grafana.ini" @@ -216,9 +185,9 @@ services: - "--prometheus-endpoint" - "http://prometheus-0:9500" - "--backend" - - etcd - - "--etcd-endpoints" - - "etcd-0:2388" + - sql + - "--sql-endpoint" + - "postgres://postgres:@postgres-0:5432/metadata" - "--state-store" - "hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001" - "--data-directory" @@ -233,13 +202,15 @@ services: - "5690:5690" - "5691:5691" depends_on: - - "etcd-0" + - "postgres-0" + - "minio-0" volumes: - "./risingwave.toml:/risingwave.toml" environment: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: meta-node-0 healthcheck: test: @@ -270,7 +241,7 @@ services: ports: - "9301:9301" - "9400:9400" - depends_on: [] + depends_on: [ ] volumes: - "minio-0:/data" entrypoint: " @@ -313,7 +284,7 @@ services: - "9500" ports: - "9500:9500" - depends_on: [] + depends_on: [ ] volumes: - "prometheus-0:/prometheus" - "./prometheus.yaml:/etc/prometheus/prometheus.yml" @@ -355,7 +326,7 @@ services: - "9092:9092" - "9644:9644" - "8081:8081" - depends_on: [] + depends_on: [ ] volumes: - "message_queue:/var/lib/redpanda/data" environment: {} @@ -367,7 +338,7 @@ services: retries: 5 restart: always volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-etcd.yml b/docker/docker-compose-etcd.yml new file mode 100644 index 0000000000000..5cca2a704d9b7 --- /dev/null +++ b/docker/docker-compose-etcd.yml @@ -0,0 +1,279 @@ +--- +version: "3" +x-image: &image + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} +services: + risingwave-standalone: + <<: *image + command: "standalone --meta-opts=\" \ + --listen-addr 0.0.0.0:5690 \ + --advertise-addr 0.0.0.0:5690 \ + --dashboard-host 0.0.0.0:5691 \ + --prometheus-host 0.0.0.0:1250 \ + --prometheus-endpoint http://prometheus-0:9500 \ + --backend etcd \ + --etcd-endpoints etcd-0:2388 \ + --state-store hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001 \ + --data-directory hummock_001 \ + --config-path /risingwave.toml\" \ + --compute-opts=\" \ + --config-path /risingwave.toml \ + --listen-addr 0.0.0.0:5688 \ + --prometheus-listener-addr 0.0.0.0:1250 \ + --advertise-addr 0.0.0.0:5688 \ + --async-stack-trace verbose \ + #--parallelism 4 \ + #--total-memory-bytes 8589934592 \ + --role both \ + --meta-address http://0.0.0.0:5690\" \ + --frontend-opts=\" \ + --config-path /risingwave.toml \ + --listen-addr 0.0.0.0:4566 \ + --advertise-addr 0.0.0.0:4566 \ + --prometheus-listener-addr 0.0.0.0:1250 \ + --health-check-listener-addr 0.0.0.0:6786 \ + --meta-addr http://0.0.0.0:5690\" \ + --compactor-opts=\" \ + --listen-addr 0.0.0.0:6660 \ + --prometheus-listener-addr 0.0.0.0:1250 \ + --advertise-addr 0.0.0.0:6660 \ + --meta-address http://0.0.0.0:5690\"" + expose: + - "6660" + - "4566" + - "5688" + - "5690" + - "1250" + - "5691" + ports: + - "4566:4566" + - "5690:5690" + - "5691:5691" + - "1250:1250" + depends_on: + - etcd-0 + - minio-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} + container_name: risingwave-standalone + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/6660; exit $$?;' + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5688; exit $$?;' + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/4566; exit $$?;' + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5690; exit $$?;' + interval: 1s + timeout: 5s + restart: always + deploy: + resources: + limits: + memory: 28G + reservations: + memory: 28G + + etcd-0: + image: "quay.io/coreos/etcd:v3.5.10" + command: + - /usr/local/bin/etcd + - "--listen-client-urls" + - "http://0.0.0.0:2388" + - "--advertise-client-urls" + - "http://etcd-0:2388" + - "--listen-peer-urls" + - "http://0.0.0.0:2389" + - "--initial-advertise-peer-urls" + - "http://etcd-0:2389" + - "--listen-metrics-urls" + - "http://0.0.0.0:2379" + - "--name" + - risedev-meta + - "--max-txn-ops" + - "999999" + - "--max-request-bytes" + - "10485760" + - "--auto-compaction-mode" + - periodic + - "--auto-compaction-retention" + - 1m + - "--snapshot-count" + - "10000" + - "--data-dir" + - /etcd-data + expose: + - "2388" + ports: + - "2388:2388" + - "2389:2389" + depends_on: [ ] + volumes: + - "etcd-0:/etcd-data" + environment: { } + container_name: etcd-0 + healthcheck: + test: + - CMD + - etcdctl + - --endpoints=http://localhost:2388 + - endpoint + - health + interval: 1s + timeout: 5s + retries: 5 + restart: always + + grafana-0: + image: "grafana/grafana-oss:latest" + command: [ ] + expose: + - "3001" + ports: + - "3001:3001" + depends_on: [ ] + volumes: + - "grafana-0:/var/lib/grafana" + - "./grafana.ini:/etc/grafana/grafana.ini" + - "./grafana-risedev-datasource.yml:/etc/grafana/provisioning/datasources/grafana-risedev-datasource.yml" + - "./grafana-risedev-dashboard.yml:/etc/grafana/provisioning/dashboards/grafana-risedev-dashboard.yml" + - "./dashboards:/dashboards" + environment: { } + container_name: grafana-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/3001; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + + minio-0: + image: "quay.io/minio/minio:latest" + command: + - server + - "--address" + - "0.0.0.0:9301" + - "--console-address" + - "0.0.0.0:9400" + - /data + expose: + - "9301" + - "9400" + ports: + - "9301:9301" + - "9400:9400" + depends_on: [ ] + volumes: + - "minio-0:/data" + entrypoint: " + + /bin/sh -c ' + + set -e + + mkdir -p \"/data/hummock001\" + + /usr/bin/docker-entrypoint.sh \"$$0\" \"$$@\" + + '" + environment: + MINIO_CI_CD: "1" + MINIO_PROMETHEUS_AUTH_TYPE: public + MINIO_PROMETHEUS_URL: "http://prometheus-0:9500" + MINIO_ROOT_PASSWORD: hummockadmin + MINIO_ROOT_USER: hummockadmin + MINIO_DOMAIN: "minio-0" + container_name: minio-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/9301; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + + prometheus-0: + image: "prom/prometheus:latest" + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.console.libraries=/usr/share/prometheus/console_libraries" + - "--web.console.templates=/usr/share/prometheus/consoles" + - "--web.listen-address=0.0.0.0:9500" + - "--storage.tsdb.retention.time=30d" + expose: + - "9500" + ports: + - "9500:9500" + depends_on: [ ] + volumes: + - "prometheus-0:/prometheus" + - "./prometheus.yaml:/etc/prometheus/prometheus.yml" + environment: { } + container_name: prometheus-0 + healthcheck: + test: + - CMD-SHELL + - sh -c 'printf "GET /-/healthy HTTP/1.0\n\n" | nc localhost 9500; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + + message_queue: + image: "docker.vectorized.io/vectorized/redpanda:latest" + command: + - redpanda + - start + - "--smp" + - "1" + - "--reserve-memory" + - 0M + - "--memory" + - 4G + - "--overprovisioned" + - "--node-id" + - "0" + - "--check=false" + - "--kafka-addr" + - "PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092" + - "--advertise-kafka-addr" + - "PLAINTEXT://message_queue:29092,OUTSIDE://localhost:9092" + expose: + - "29092" + - "9092" + - "9644" + ports: + - "29092:29092" + - "9092:9092" + - "9644:9644" + - "8081:8081" + depends_on: [ ] + volumes: + - "message_queue:/var/lib/redpanda/data" + environment: { } + container_name: message_queue + healthcheck: + test: curl -f localhost:9644/v1/status/ready + interval: 1s + timeout: 5s + retries: 5 + restart: always +volumes: + etcd-0: + external: false + grafana-0: + external: false + minio-0: + external: false + prometheus-0: + external: false + message_queue: + external: false diff --git a/docker/docker-compose-with-azblob.yml b/docker/docker-compose-with-azblob.yml index e0b44c5768011..ed056d9ad1c53 100644 --- a/docker/docker-compose-with-azblob.yml +++ b/docker/docker-compose-with-azblob.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -11,8 +10,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+azblob:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +50,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -59,6 +58,7 @@ services: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-gcs.yml b/docker/docker-compose-with-gcs.yml index 847172c2d09c1..28e52286df395 100644 --- a/docker/docker-compose-with-gcs.yml +++ b/docker/docker-compose-with-gcs.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -11,8 +10,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+gcs:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +50,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -59,6 +58,7 @@ services: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-hdfs.yml b/docker/docker-compose-with-hdfs.yml index cf2b45078bac5..fc0fbb4067276 100644 --- a/docker/docker-compose-with-hdfs.yml +++ b/docker/docker-compose-with-hdfs.yml @@ -1,5 +1,4 @@ --- -version: "3" services: compactor-0: image: ghcr.io/risingwavelabs/risingwave:RisingWave_1.6.1_HDFS_2.7-x86_64 @@ -18,7 +17,7 @@ services: expose: - "6660" - "1260" - ports: [] + ports: [ ] depends_on: - meta-node-0 volumes: @@ -58,7 +57,7 @@ services: expose: - "5688" - "1222" - ports: [] + ports: [ ] depends_on: - meta-node-0 volumes: @@ -114,7 +113,7 @@ services: ports: - "2388:2388" - "2389:2389" - depends_on: [] + depends_on: [ ] volumes: - "etcd-0:/etcd-data" environment: @@ -172,19 +171,19 @@ services: memory: 1G grafana-0: image: "grafana/grafana-oss:latest" - command: [] + command: [ ] expose: - "3001" ports: - "3001:3001" - depends_on: [] + depends_on: [ ] volumes: - "grafana-0:/var/lib/grafana" - "./grafana.ini:/etc/grafana/grafana.ini" - "./grafana-risedev-datasource.yml:/etc/grafana/provisioning/datasources/grafana-risedev-datasource.yml" - "./grafana-risedev-dashboard.yml:/etc/grafana/provisioning/dashboards/grafana-risedev-dashboard.yml" - "./dashboards:/dashboards" - environment: {} + environment: { } container_name: grafana-0 healthcheck: test: @@ -232,6 +231,7 @@ services: - ":/opt/hadoop" environment: - HADOOP_HOME=/opt/hadoop/ + - RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: meta-node-0 healthcheck: test: @@ -260,11 +260,11 @@ services: - "9500" ports: - "9500:9500" - depends_on: [] + depends_on: [ ] volumes: - "prometheus-0:/prometheus" - "./prometheus.yaml:/etc/prometheus/prometheus.yml" - environment: {} + environment: { } container_name: prometheus-0 healthcheck: test: @@ -302,10 +302,10 @@ services: - "9092:9092" - "9644:9644" - "8081:8081" - depends_on: [] + depends_on: [ ] volumes: - "message_queue:/var/lib/redpanda/data" - environment: {} + environment: { } container_name: message_queue healthcheck: test: curl -f localhost:9644/v1/status/ready diff --git a/docker/docker-compose-with-local-fs.yml b/docker/docker-compose-with-local-fs.yml index b45e624c619b3..d6f0699c22b28 100644 --- a/docker/docker-compose-with-local-fs.yml +++ b/docker/docker-compose-with-local-fs.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:nightly-20231211} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -11,8 +10,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+fs:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -50,13 +49,14 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 volumes: - "./risingwave.toml:/risingwave.toml" environment: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -74,10 +74,10 @@ services: memory: reservations: memory: - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -87,7 +87,7 @@ services: file: docker-compose.yml service: prometheus-0 volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-obs.yml b/docker/docker-compose-with-obs.yml index 5d0df0ca4f72d..72201df7e7dd6 100644 --- a/docker/docker-compose-with-obs.yml +++ b/docker/docker-compose-with-obs.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -11,8 +10,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+obs:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +50,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -59,6 +58,7 @@ services: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-oss.yml b/docker/docker-compose-with-oss.yml index 7296a7074d5a6..1fba5ea52f348 100644 --- a/docker/docker-compose-with-oss.yml +++ b/docker/docker-compose-with-oss.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -11,8 +10,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+oss:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +50,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -59,6 +58,7 @@ services: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-s3.yml b/docker/docker-compose-with-s3.yml index 815489f82493e..a0cdcf5ef73de 100644 --- a/docker/docker-compose-with-s3.yml +++ b/docker/docker-compose-with-s3.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -11,8 +10,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+s3:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +50,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: aws.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -59,6 +58,7 @@ services: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-sqlite.yml b/docker/docker-compose-with-sqlite.yml index c303a09b2b4f9..0c3a04a661f33 100644 --- a/docker/docker-compose-with-sqlite.yml +++ b/docker/docker-compose-with-sqlite.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -59,6 +58,7 @@ services: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -79,19 +79,19 @@ services: grafana-0: image: "grafana/grafana-oss:latest" - command: [] + command: [ ] expose: - "3001" ports: - "3001:3001" - depends_on: [] + depends_on: [ ] volumes: - "grafana-0:/var/lib/grafana" - "./grafana.ini:/etc/grafana/grafana.ini" - "./grafana-risedev-datasource.yml:/etc/grafana/provisioning/datasources/grafana-risedev-datasource.yml" - "./grafana-risedev-dashboard.yml:/etc/grafana/provisioning/dashboards/grafana-risedev-dashboard.yml" - "./dashboards:/dashboards" - environment: {} + environment: { } container_name: grafana-0 healthcheck: test: @@ -117,7 +117,7 @@ services: ports: - "9301:9301" - "9400:9400" - depends_on: [] + depends_on: [ ] volumes: - "minio-0:/data" entrypoint: " @@ -161,11 +161,11 @@ services: - "9500" ports: - "9500:9500" - depends_on: [] + depends_on: [ ] volumes: - "prometheus-0:/prometheus" - "./prometheus.yaml:/etc/prometheus/prometheus.yml" - environment: {} + environment: { } container_name: prometheus-0 healthcheck: test: @@ -204,10 +204,10 @@ services: - "9092:9092" - "9644:9644" - "8081:8081" - depends_on: [] + depends_on: [ ] volumes: - "message_queue:/var/lib/redpanda/data" - environment: {} + environment: { } container_name: message_queue healthcheck: test: curl -f localhost:9644/v1/status/ready diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6259a5757b14f..dc9ad11f0eebc 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,7 +1,6 @@ --- -version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.10.0-rc.1} services: risingwave-standalone: <<: *image @@ -11,8 +10,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001 \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +50,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 - minio-0 volumes: - "./risingwave.toml:/risingwave.toml" @@ -59,6 +58,7 @@ services: RUST_BACKTRACE: "1" # If ENABLE_TELEMETRY is not set, telemetry will start by default ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + RW_TELEMETRY_TYPE: ${RW_TELEMETRY_TYPE:-"docker-compose"} container_name: risingwave-standalone healthcheck: test: @@ -77,71 +77,41 @@ services: reservations: memory: 28G - etcd-0: - image: "quay.io/coreos/etcd:v3.5.10" - command: - - /usr/local/bin/etcd - - "--listen-client-urls" - - "http://0.0.0.0:2388" - - "--advertise-client-urls" - - "http://etcd-0:2388" - - "--listen-peer-urls" - - "http://0.0.0.0:2389" - - "--initial-advertise-peer-urls" - - "http://etcd-0:2389" - - "--listen-metrics-urls" - - "http://0.0.0.0:2379" - - "--name" - - risedev-meta - - "--max-txn-ops" - - "999999" - - "--max-request-bytes" - - "10485760" - - "--auto-compaction-mode" - - periodic - - "--auto-compaction-retention" - - 1m - - "--snapshot-count" - - "10000" - - "--data-dir" - - /etcd-data + postgres-0: + image: "postgres:15-alpine" + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + - POSTGRES_USER=postgres + - POSTGRES_DB=metadata + - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C expose: - - "2388" + - "5432" ports: - - "2388:2388" - - "2389:2389" - depends_on: [] + - "8432:5432" volumes: - - "etcd-0:/etcd-data" - environment: {} - container_name: etcd-0 + - "postgres-0:/var/lib/postgresql/data" healthcheck: - test: - - CMD - - etcdctl - - --endpoints=http://localhost:2388 - - endpoint - - health - interval: 1s + test: [ "CMD-SHELL", "pg_isready -U postgres" ] + interval: 2s timeout: 5s retries: 5 restart: always grafana-0: image: "grafana/grafana-oss:latest" - command: [] + command: [ ] expose: - "3001" ports: - "3001:3001" - depends_on: [] + depends_on: [ ] volumes: - "grafana-0:/var/lib/grafana" - "./grafana.ini:/etc/grafana/grafana.ini" - "./grafana-risedev-datasource.yml:/etc/grafana/provisioning/datasources/grafana-risedev-datasource.yml" - "./grafana-risedev-dashboard.yml:/etc/grafana/provisioning/dashboards/grafana-risedev-dashboard.yml" - "./dashboards:/dashboards" - environment: {} + environment: { } container_name: grafana-0 healthcheck: test: @@ -167,7 +137,7 @@ services: ports: - "9301:9301" - "9400:9400" - depends_on: [] + depends_on: [ ] volumes: - "minio-0:/data" entrypoint: " @@ -211,11 +181,11 @@ services: - "9500" ports: - "9500:9500" - depends_on: [] + depends_on: [ ] volumes: - "prometheus-0:/prometheus" - "./prometheus.yaml:/etc/prometheus/prometheus.yml" - environment: {} + environment: { } container_name: prometheus-0 healthcheck: test: @@ -254,10 +224,10 @@ services: - "9092:9092" - "9644:9644" - "8081:8081" - depends_on: [] + depends_on: [ ] volumes: - "message_queue:/var/lib/redpanda/data" - environment: {} + environment: { } container_name: message_queue healthcheck: test: curl -f localhost:9644/v1/status/ready @@ -266,7 +236,7 @@ services: retries: 5 restart: always volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docs/README.md b/docs/README.md index 006cac7f6adbb..e905cea7849ea 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ This directory contains RisingWave design documents that are intended to be used ## Developer guide -After you learn about the basics of RisingWave, take a look at our [developer guide](developer-guide.md). It'll help you up to speed with the development process. +After you learn about the basics of RisingWave, take a look at our [developer guide](https://risingwavelabs.github.io/risingwave/) to get up to speed with the development process. ## Table of Contents diff --git a/docs/backfill.md b/docs/backfill.md index 6784070853712..aac20615caf10 100644 --- a/docs/backfill.md +++ b/docs/backfill.md @@ -358,6 +358,22 @@ and arrangement backfill will consume this historical data snapshot: | 1 | 'Jack' | 29 | | 2 | 'Jill' | 30 | +#### Initialization + +Something to note is that for the first snapshot, +upstream may not have finished committing data in that epoch to s3. + +Additionally, we have not replicated any upstream records +during that epoch, only in the subsequent ones. + +As such, we must wait for that first checkpoint to be committed, +before reading, or we risk missing the uncommitted data in our backfill. + +This is supported internally inside `init_epoch` for replicated state table. +```shell + upstream_table.init_epoch(first_epoch).await?; +``` + ### Recovery TODO diff --git a/docs/dev/README.md b/docs/dev/README.md new file mode 100644 index 0000000000000..e19f10c08e3c3 --- /dev/null +++ b/docs/dev/README.md @@ -0,0 +1,30 @@ +# RisingWave Developer guide + +This is the source of the RisingWave Developer guide, published at + (via GitHub Actions to GitHub Pages). +(Note: the url was previously for the crate rustdocs, and now they are moved to ) + +The book is written in Markdown, using the [mdbook](https://rust-lang.github.io/mdBook/) tool to convert to HTML. + +Edit `SUMMARY.md` to add new chapters. + +## Building the book + +```sh +# install tools +> cargo binstall mdbook mdbook-linkcheck mdbook-toc +# start a web server on localhost that you can visit to view the book, +# and it will automatically reload each time you edit a page. +> mdbook serve --open +``` + +## Table of Contents + +We use `mdbook-toc` to auto-generate TOCs for long sections. You can invoke the preprocessor by +including the `` marker at the place where you want the TOC. + + +## Link Validations + +We use `mdbook-linkcheck` to validate URLs included in our documentation. +`linkcheck` will be run automatically when you build with the instructions in the section above. diff --git a/docs/dev/book.toml b/docs/dev/book.toml new file mode 100644 index 0000000000000..77608409452b0 --- /dev/null +++ b/docs/dev/book.toml @@ -0,0 +1,15 @@ +[book] +authors = ["xxchan"] +title = "RisingWave Developer Guide" + +[preprocessor.toc] +command = "mdbook-toc" +renderer = ["html"] + +[output.html] +smart-punctuation = true +git-repository-url = "https://github.com/risingwavelabs/risingwave/tree/main/docs/dev/src" +edit-url-template = "https://github.com/risingwavelabs/risingwave/edit/main/docs/dev/{path}" +search.use-boolean-and = true + +[output.linkcheck] diff --git a/docs/dev/src/SUMMARY.md b/docs/dev/src/SUMMARY.md new file mode 100644 index 0000000000000..76cf57c007c23 --- /dev/null +++ b/docs/dev/src/SUMMARY.md @@ -0,0 +1,42 @@ +# Summary + +[Introduction](./intro.md) +[Contribution Guidelines](./contributing.md) + +--- + +# Building and debugging RisingWave + +- [Building and Running](./build-and-run/intro.md) +- [Testing](./tests/intro.md) +- [Debugging](./debugging.md) +- [Observability](./observability.md) + +--- + +# Benchmarking and Profiling + +- [CPU Profiling](./benchmark-and-profile/cpu-profiling.md) +- [Memory (Heap) Profiling](./benchmark-and-profile/memory-profiling.md) +- [Microbench](./benchmark-and-profile/microbenchmarks.md) + +--- + +# Specialized topics + +- [Develop Connectors](./connector/intro.md) +- [Continuous Integration](./ci.md) + + + +## CI Labels Guide + +- `[ci/run-xxx ...]`: Run additional steps in the PR workflow indicated by `ci/run-xxx` in your PR. +- `ci/pr/run-selected` + `[ci/run-xxx ...]` : Only run selected steps indicated by `ci/run-xxx` in your **DRAFT PR.** +- `ci/main-cron/run-all`: Run full `main-cron` workflow for your PR. +- `ci/main-cron/run-selected` + `[ci/run-xxx …]` : Run specific steps indicated by `ci/run-xxx` + from the `main-cron` workflow, in your PR. Can use to verify some `main-cron` fix works as expected. +- To reference `[ci/run-xxx ...]` labels, you may look at steps from `pull-request.yml` and `main-cron.yml`. + +### Example + + + +To run `e2e-test` and `e2e-source-test` for `main-cron` in your pull request: +1. Add `ci/run-e2e-test`. +2. Add `ci/run-e2e-source-tests`. +3. Add `ci/main-cron/run-selected` to skip all other steps which were not selected with `ci/run-xxx`. diff --git a/docs/dev/src/connector/intro.md b/docs/dev/src/connector/intro.md new file mode 100644 index 0000000000000..88d487fe96c47 --- /dev/null +++ b/docs/dev/src/connector/intro.md @@ -0,0 +1,191 @@ +# Develop Connectors + +RisingWave supports a lot of connectors (sources and sinks). +However, developing connectors is tricky because it involves external systems: + +- Before developing and test, it's troublesome to set up the development environment +- During testing, we need to seed the external system with test data (perhaps with some scripts) +- The test relies on the configuration of the setup. e.g., the test needs to know the port of your Kafka in order to +- We need to do the setup for both CI and local development. + +Our solution is: we resort to RiseDev, our all-in-one development tool, to help manage external systems and solve these problems. + +Before going to the specific methods described in the sections below, the principles we should follow are: +- *environment-independent*: It should easy to start cluster and run tests on your own machine, other developers' machines, and CI. + * Don't use hard-coded configurations (e.g., `localhost:9092` for Kafka). + * Don't write too many logic in `ci/scripts`. Let CI scripts be thin wrappers. +- *self-contained* tests: It should be straightforward to run one test case, without worrying about where is the script to prepare the test. + * Don't put setup logic, running logic and verification logic of a test in different places. + +Reference: for the full explanations of the difficulies and the design of our solution, see [here](https://github.com/risingwavelabs/risingwave/issues/12451#issuecomment-2051861048). + +The following sections first walk you through what is the development workflow for +existing connectors, and finally explain how to extend the development framework to support a new connector. + + + +## Set up the development environment + +RiseDev supports starting external connector systems (e.g., Kafka, MySQL) just like how it starts the RisingWave cluster, and other standard systems used as part of the RisingWave Cluster (e.g., MinIO, etcd, Grafana). + +You write the profile in `risedev.yml` (Or `risedev-profiles.user.yml` ), e.g., the following config includes Kafka and MySQL, which will be used to test sources. + +```yml + my-cool-profile: + steps: + # RisingWave cluster + - use: minio + - use: etcd + - use: meta-node + meta-backend: etcd + - use: compute-node + - use: frontend + - use: compactor + # Connectors + - use: kafka + address: message_queue + port: 29092 + - use: mysql + port: 3306 + address: mysql + user: root + password: 123456 +``` + +Then + +```sh +# will start the cluster along with Kafka and MySQL for you +risedev d my-cool-profile +``` + +For all config options of supported systems, check the comments in `template` section of `risedev.yml` . + +### Escape hatch: `user-managed` mode + +`user-managed` is a special config. When set to `true` , you will need to start the system by yourself. You may wonder why bother to add it to the RiseDev profile if you start it by yourself. In this case, the config will still be loaded by RiseDev, which will be useful in tests. See chapters below for more details. + +The `user-managed` mode can be used as a workaround to start a system that is not yet supported by RiseDev, or is buggy. It's also used to config the CI profile. (In CI, all services are pre-started by `ci/docker-compose.yml` ) + +Example of the config: + +```yml + - use: kafka + user-managed: true + address: message_queue + port: 29092 +``` + +## End-to-end tests + +The e2e tests are written in `slt` files. There are 2 main points to note: +1. Use `system ok` to run `bash` commands to interact with external systems. + Use this to prepare the test data, and verify the results. The whole lifecycle of + a test case should be written in the same `slt` file. +2. Use `control substitution on` and then use environment variables to specify the config of the external systems, e.g., the port of Kafka. + +Refer to the [sqllogictest-rs documentation](https://github.com/risinglightdb/sqllogictest-rs#extension-run-external-shell-commands) for the details of `system` and `substitution` . + +--- + +Take Kafka as an example about how to the tests are written: + +When you use `risedev d` to start the external services, related environment variables for Kafka will be available when you run `risedev slt`: + +```sh +RISEDEV_KAFKA_BOOTSTRAP_SERVERS="127.0.0.1:9092" +RISEDEV_KAFKA_WITH_OPTIONS_COMMON="connector='kafka',properties.bootstrap.server='127.0.0.1:9092'" +RPK_BROKERS="127.0.0.1:9092" +``` + +The `slt` test case looks like this: + +``` +control substitution on + +# Note: you can also use envvars in `system` commands, but usually it's not necessary since the CLI tools can load envvars themselves. +system ok +rpk topic create my_source -p 4 + +# Prepared test topic above, and produce test data now +system ok +cat << EOF | rpk topic produce my_source -f "%p %v\n" -p 0 +0 {"v1": 1, "v2": "a"} +1 {"v1": 2, "v2": "b"} +2 {"v1": 3, "v2": "c"} +EOF + +# Create the source, connecting to the Kafka started by RiseDev +statement ok +create source s0 (v1 int, v2 varchar) with ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'my_source', + scan.startup.mode = 'earliest' +) FORMAT PLAIN ENCODE JSON; +``` + +See `src/risedevtool/src/risedev_env.rs` for variables supported for each service. + +> Note again: You need to use `risedev d` to start the cluster, and then use `risedev slt` to run the tests. It doesn't work if you start the cluster by yourself without telling RiseDev, or you use raw `sqllogictest` binary directly. +> +> How it works: `risedev d` will write env vars to `.risingwave/config/risedev-env`, +> and `risedev slt` will load env vars from this file. + +### Tips for writing `system` commands + +Refer to the [sqllogictest-rs documentation](https://github.com/risinglightdb/sqllogictest-rs#extension-run-external-shell-commands) for the syntax. + +For simple cases, you can directly write a bash command, e.g., +``` +system ok +mysql -e " + DROP DATABASE IF EXISTS testdb1; CREATE DATABASE testdb1; + USE testdb1; + CREATE TABLE tt1 (v1 int primary key, v2 timestamp); + INSERT INTO tt1 VALUES (1, '2023-10-23 10:00:00'); +" + +system ok +cat << EOF | rpk topic produce my_source -f "%p %v\n" -p 0 +0 {"v1": 1, "v2": "a"} +1 {"v1": 2, "v2": "b"} +2 {"v1": 3, "v2": "c"} +EOF +``` + +For more complex cases, you can write a test script, and invoke it in `slt`. Scripts can be written in any language you like, but kindly write a `README.md` to help other developers get started more easily. +- For ad-hoc scripts (only used for one test), it's better to put next to the test file. + + e.g., [`e2e_test/source_inline/kafka/consumer_group.mjs`](https://github.com/risingwavelabs/risingwave/blob/c22c4265052c2a4f2876132a10a0b522ec7c03c9/e2e_test/source_inline/kafka/consumer_group.mjs), which is invoked by [`consumer_group.slt`](https://github.com/risingwavelabs/risingwave/blob/c22c4265052c2a4f2876132a10a0b522ec7c03c9/e2e_test/source_inline/kafka/consumer_group.slt) next to it. +- For general scripts that can be used under many situations, put it in `e2e_test/commands/`. This directory will be loaded in `PATH` by `risedev slt`, and thus function as kind of "built-in" commands. + + A common scenario is when a CLI tool does not accept envvars as arguments. In such cases, instead of manually specifying the arguments each time invoking it in `slt`, you can create a wrapper to handle this implicitly, making it more concise. [`e2e_test/commands/mysql`](https://github.com/risingwavelabs/risingwave/blob/c22c4265052c2a4f2876132a10a0b522ec7c03c9/e2e_test/commands/mysql) is a good demonstration. + +--- +Tips for debugging: + +- Use `echo` to check whether the environment is correctly set. + + ``` + system ok + echo $PGPORT + ---- + placeholder + ``` + + Then running `risedev slt` will return error "result mismatch", and shows what's the output + of the `echo` command, i.e., the value of `PGPORT`. + +- Use `risedev show-risedev-env` to see the environment variables available for `risedev slt`, after you starting the cluster with `risedev d`. + +## Adding a new connector to the development framework + +Refer to [#16449](https://github.com/risingwavelabs/risingwave/pull/16449) ( `user-managed` only MySQL), and [#16514](https://github.com/risingwavelabs/risingwave/pull/16514) (Docker based MySQL) as examples. + +1. Add a new service in `template` section of `risedev.yml`. + And add corresponding config in `src/risedevtool/src/service_config.rs` . +2. Implement the new service task, and add it to `src/risedevtool/src/bin/risedev-dev.rs`. +3. Add environment variables you want to use in the `slt` tests in `src/risedevtool/src/risedev_env.rs`. +4. Write tests according to the style explained in the previous section. + + diff --git a/docs/dev/src/contributing.md b/docs/dev/src/contributing.md new file mode 100644 index 0000000000000..38d8b8cc77c33 --- /dev/null +++ b/docs/dev/src/contributing.md @@ -0,0 +1,64 @@ +# Contribution guidelines + +Thanks for your interest in contributing to RisingWave! We welcome and appreciate contributions. + +This document describes how to submit your code changes. To learn about the development process, see other chapters of the book. To understand the design and implementation of RisingWave, refer to the design docs listed in [docs/README.md](https://github.com/risingwavelabs/risingwave/blob/fb60113c2e8a7f0676af545c99f073a335c255f3/docs/README.md). + + +If you have questions, you can search for existing discussions or start a new discussion in the [Discussions forum of RisingWave](https://github.com/risingwavelabs/risingwave/discussions), or ask in the RisingWave Community channel on Slack. Please use the [invitation link](https://risingwave.com/slack) to join the channel. + +To report bugs, create a [GitHub issue](https://github.com/risingwavelabs/risingwave/issues/new/choose). + + + +## Tests and miscellaneous checks + +Before submitting your code changes, ensure you fully test them and perform necessary checks. The testing instructions and necessary checks are detailed in other sections of the book. + +## Submit a PR + +### Pull Request title + +As described in [here](https://github.com/commitizen/conventional-commit-types/blob/master/index.json), a valid PR title should begin with one of the following prefixes: + +- `feat`: A new feature +- `fix`: A bug fix +- `doc`: Documentation only changes +- `refactor`: A code change that neither fixes a bug nor adds a feature +- `style`: A refactoring that improves code style +- `perf`: A code change that improves performance +- `test`: Adding missing tests or correcting existing tests +- `build`: Changes that affect the build system or external dependencies (example scopes: `.config`, `.cargo`, `Cargo.toml`) +- `ci`: Changes to RisingWave CI configuration files and scripts (example scopes: `.github`, `ci` (Buildkite)) +- `chore`: Other changes that don't modify src or test files +- `revert`: Reverts a previous commit + +For example, a PR title could be: + +- `refactor: modify executor protobuf package path` +- `feat(execution): enable comparison between nullable data arrays`, where `(execution)` means that this PR mainly focuses on the execution component. + +You may also check out previous PRs in the [PR list](https://github.com/risingwavelabs/risingwave/pulls). + +### Pull Request description + +- If your PR is small (such as a typo fix), you can go brief. +- If it is large and you have changed a lot, it's better to write more details. + +### Sign the CLA + +Contributors will need to sign RisingWave Labs' CLA. + +### Cherry pick the commit to release candidate branch + +We have a GitHub Action to help cherry-pick commits from `main` branch to a `release candidate` branch, such as `v*.*.*-rc` where `*` is a number. + +Checkout details at: + +To trigger the action, we give a correct label to the PR on `main` branch : + + +It will act when the PR on `main` branch merged: +- If `git cherry-pick` does not find any conflicts, it will open a PR to the `release candidate` branch, and assign the original author as the reviewer. + +- If there is a conflict, it will open an issue and make the original author the assignee. diff --git a/docs/dev/src/debugging.md b/docs/dev/src/debugging.md new file mode 100644 index 0000000000000..4f5a8b8d454a3 --- /dev/null +++ b/docs/dev/src/debugging.md @@ -0,0 +1,8 @@ +# Debugging + + +## Debug playground using vscode + +To step through risingwave locally with a debugger you can use the `launch.json` and the `tasks.json` provided in `vscode_suggestions`. After adding these files to your local `.vscode` folder you can debug and set breakpoints by launching `Launch 'risingwave p' debug`. + + diff --git a/docs/dev/src/intro.md b/docs/dev/src/intro.md new file mode 100644 index 0000000000000..c4317c8d71dd3 --- /dev/null +++ b/docs/dev/src/intro.md @@ -0,0 +1,25 @@ +# Introduction + +This guide is intended to be used by contributors to learn about how to develop RisingWave. The instructions about how to submit code changes are included in [contributing guidelines](./contributing.md). + +If you have questions, you can search for existing discussions or start a new discussion in the [Discussions forum of RisingWave](https://github.com/risingwavelabs/risingwave/discussions), or ask in the RisingWave Community channel on Slack. Please use the [invitation link](https://risingwave.com/slack) to join the channel. + +To report bugs, create a [GitHub issue](https://github.com/risingwavelabs/risingwave/issues/new/choose). + +Note: the url was previously for the crate rustdocs, and now they are moved to path [`/risingwave/rustdoc`](https://risingwavelabs.github.io/risingwave/rustdoc) + +## Read the design docs + + +Before you start to make code changes, ensure that you understand the design and implementation of RisingWave. We recommend that you read the design docs listed in [docs/README.md](https://github.com/risingwavelabs/risingwave/blob/fb60113c2e8a7f0676af545c99f073a335c255f3/docs/README.md) first. + +You can also read the [crate level documentation](https://risingwavelabs.github.io/risingwave/rustdoc) for implementation details, or run `./risedev doc` to read it locally. + +## Learn about the code structure + + +- The `src` folder contains all of the kernel components, refer to [src/README.md](https://github.com/risingwavelabs/risingwave/blob/fb60113c2e8a7f0676af545c99f073a335c255f3/src/README.md) for more details, which contains more details about Design Patterns in RisingWave. +- The `docker` folder contains Docker files to build and start RisingWave. +- The `e2e_test` folder contains the latest end-to-end test cases. +- The `docs` folder contains the design docs. If you want to learn about how RisingWave is designed and implemented, check out the design docs here. +- The `dashboard` folder contains RisingWave dashboard. diff --git a/docs/dev/src/observability.md b/docs/dev/src/observability.md new file mode 100644 index 0000000000000..1d1d2a4c06a36 --- /dev/null +++ b/docs/dev/src/observability.md @@ -0,0 +1,55 @@ +# Observability + +RiseDev supports several observability components. + +## Cluster Control + +`risectl` is the tool for providing internal access to the RisingWave cluster. See + +``` +cargo run --bin risectl -- --help +``` + +... or + +``` +./risedev ctl --help +``` + +for more information. + +## Monitoring + +Uncomment `grafana` and `prometheus` lines in `risedev.yml` to enable Grafana and Prometheus services. + +## Tracing + +Compute nodes support streaming tracing. Tracing is not enabled by default. You need to +use `./risedev configure` to download the tracing components first. After that, you will need to uncomment `tempo` + +service in `risedev.yml` and start a new dev cluster to allow the components to work. + +Traces are visualized in Grafana. You may also want to uncomment `grafana` service in `risedev.yml` to enable it. + +## Dashboard + +You may use RisingWave Dashboard to see actors in the system. It will be started along with meta node, and available at `http://127.0.0.1:5691/` . + +The development instructions for dashboard are available [here](https://github.com/risingwavelabs/risingwave/blob/fb60113c2e8a7f0676af545c99f073a335c255f3/dashboard/README.md). + +## Logging + +The Rust components use `tokio-tracing` to handle both logging and tracing. The default log level is set as: + +* Third-party libraries: warn +* Other libraries: debug + +To configure log levels, launch RisingWave with the environment variable `RUST_LOG` set as described [here](https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/filter/struct.EnvFilter.html). + +There're also some logs designated for debugging purposes with target names starting with `events::` . +For example, by setting `RUST_LOG=events::stream::message::chunk=trace` , all chunk messages will be logged as it passes through the executors in the streaming engine. Search in the codebase to find more of them. + + + diff --git a/docs/dev/src/tests/intro.md b/docs/dev/src/tests/intro.md new file mode 100644 index 0000000000000..7e99ca9b20884 --- /dev/null +++ b/docs/dev/src/tests/intro.md @@ -0,0 +1,199 @@ +# Testing + +Before you submit a PR, fully test the code changes and perform necessary checks. + +The RisingWave project enforces several checks in CI. Every time the code is modified, you need to perform the checks and ensure they pass. + + + + +## Lint + +RisingWave requires all code to pass fmt, clippy, sort and hakari checks. Run the following commands to install test tools and perform these checks. + +```shell +./risedev install-tools # Install required tools for running unit tests +./risedev c # Run all checks. Shortcut for ./risedev check +``` + +There are also some miscellaneous checks. See `ci/scripts/misc-check.sh`. + +### Unit and integration tests + +RiseDev runs unit tests with cargo-nextest. To run unit tests: + +```shell +./risedev test # Run unit tests +``` + +Some ideas and caveats for writing tests: +- Use [expect_test](https://github.com/rust-analyzer/expect-test) to write data driven tests that can automatically update results. +- It's recommended to write new tests as *integration tests* (i.e. in `tests/` directory) instead of *unit tests* (i.e. in `src/` directory). + + Besides, put integration tests under `tests/integration_tests/*.rs`, instead of `tests/*.rs`. See [Delete Cargo Integration Tests](https://matklad.github.io/2021/02/27/delete-cargo-integration-tests.html) and [#9878](https://github.com/risingwavelabs/risingwave/issues/9878), for more details. + +You might want to read [How to Test](https://matklad.github.io/2021/05/31/how-to-test.html) for more good ideas on testing. + +## Planner tests + +RisingWave's SQL frontend has SQL planner tests. + + + + +## End-to-end tests + +We use [sqllogictest-rs](https://github.com/risinglightdb/sqllogictest-rs) to run RisingWave e2e tests. + +Refer to Sqllogictest [`.slt` Test File Format Cookbook](https://github.com/risinglightdb/sqllogictest-rs#slt-test-file-format-cookbook) for the syntax. + +Before running end-to-end tests, you will need to start a full cluster first: + +```shell +./risedev d +``` + +Then to run the end-to-end tests, you can use one of the following commands according to which component you are developing: + +```shell +# run all streaming tests +./risedev slt-streaming -p 4566 -d dev -j 1 +# run all batch tests +./risedev slt-batch -p 4566 -d dev -j 1 +# run both +./risedev slt-all -p 4566 -d dev -j 1 +``` + +> Use `-j 1` to create a separate database for each test case, which can ensure that previous test case failure won't affect other tests due to table cleanups. + +Alternatively, you can also run some specific tests: + +```shell +# run a single test +./risedev slt -p 4566 -d dev './e2e_test/path/to/file.slt' +# run all tests under a directory (including subdirectories) +./risedev slt -p 4566 -d dev './e2e_test/path/to/directory/**/*.slt' +``` + +After running e2e tests, you may kill the cluster and clean data. + +```shell +./risedev k # shortcut for ./risedev kill +./risedev clean-data +``` + +RisingWave's codebase is constantly changing. The persistent data might not be stable. In case of unexpected decode errors, try `./risedev clean-data` first. + +## Fuzzing tests + +### SqlSmith + +Currently, SqlSmith supports for e2e and frontend fuzzing. Take a look at [Fuzzing tests](https://github.com/risingwavelabs/risingwave/blob/fb60113c2e8a7f0676af545c99f073a335c255f3/src/tests/sqlsmith/README.md#L1) for more details on running it locally. + + + +## DocSlt tests + +As introduced in [#5117](https://github.com/risingwavelabs/risingwave/issues/5117), DocSlt tool allows you to write SQL examples in sqllogictest syntax in Rust doc comments. After adding or modifying any such SQL examples, you should run the following commands to generate and run e2e tests for them. + +```shell +# generate e2e tests from doc comments for all default packages +./risedev docslt +# or, generate for only modified package +./risedev docslt -p risingwave_expr + +# run all generated e2e tests +./risedev slt-generated -p 4566 -d dev +# or, run only some of them +./risedev slt -p 4566 -d dev './e2e_test/generated/docslt/risingwave_expr/**/*.slt' +``` + +These will be run on CI as well. + +## Deterministic simulation tests + +Deterministic simulation is a powerful tool to efficiently search bugs and reliably reproduce them. +In case you are not familiar with this technique, here is a [talk](https://www.youtube.com/watch?v=4fFDFbi3toc) and a [blog post](https://sled.rs/simulation.html) for brief introduction. + +See also the blog posts for a detailed writeup: +- [Deterministic Simulation: A New Era of Distributed System Testing (Part 1 of 2)](https://www.risingwave.com/blog/deterministic-simulation-a-new-era-of-distributed-system-testing/) +- [Applying Deterministic Simulation: The RisingWave Story (Part 2 of 2)](https://www.risingwave.com/blog/applying-deterministic-simulation-the-risingwave-story-part-2-of-2/) + +In RisingWave, deterministic simulation is supported in both unit test and end-to-end test. You can run them using the following commands: + +```sh +# run deterministic unit test +./risedev stest +# run deterministic end-to-end test +./risedev sslt -- './e2e_test/path/to/directory/**/*.slt' +``` + +When your program panics, the simulator will print the random seed of this run: + +```sh +thread '' panicked at '...', +note: run with `MADSIM_TEST_SEED=1` environment variable to reproduce this error +``` + +Then you can reproduce the bug with the given seed: + +```sh +# set the random seed to reproduce a run +MADSIM_TEST_SEED=1 RUST_LOG=info ./risedev sslt -- './e2e_test/path/to/directory/**/*.slt' +``` + +More advanced usages are listed below: + +```sh +# run multiple times with different seeds to test reliability +# it's recommended to build in release mode for a fast run +MADSIM_TEST_NUM=100 ./risedev sslt --release -- './e2e_test/path/to/directory/**/*.slt' + +# configure cluster nodes (by default: 2fe+3cn) +./risedev sslt -- --compute-nodes 2 './e2e_test/path/to/directory/**/*.slt' + +# inject failures to test fault recovery +./risedev sslt -- --kill-meta --etcd-timeout-rate=0.01 './e2e_test/path/to/directory/**/*.slt' + +# see more usages +./risedev sslt -- --help +``` + +Deterministic test is included in CI as well. See [CI script](https://github.com/risingwavelabs/risingwave/blob/fb60113c2e8a7f0676af545c99f073a335c255f3/ci/scripts/deterministic-e2e-test.sh) for details. + +## Deterministic Simulation Integration tests + +To run these tests: +```shell +./risedev sit-test +``` + +Sometimes in CI you may see a backtrace, followed by an error message with a `MADSIM_TEST_SEED`: +```shell + 161: madsim::sim::task::Executor::block_on + at /risingwave/.cargo/registry/src/index.crates.io-6f17d22bba15001f/madsim-0.2.22/src/sim/task/mod.rs:238:13 + 162: madsim::sim::runtime::Runtime::block_on + at /risingwave/.cargo/registry/src/index.crates.io-6f17d22bba15001f/madsim-0.2.22/src/sim/runtime/mod.rs:126:9 + 163: madsim::sim::runtime::builder::Builder::run::{{closure}}::{{closure}}::{{closure}} + at /risingwave/.cargo/registry/src/index.crates.io-6f17d22bba15001f/madsim-0.2.22/src/sim/runtime/builder.rs:128:35 +note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. +context: node=6 "compute-1", task=2237 (spawned at /risingwave/src/stream/src/task/stream_manager.rs:689:34) +note: run with `MADSIM_TEST_SEED=2` environment variable to reproduce this error +``` + +You may use that to reproduce it in your local environment. For example: +```shell +MADSIM_TEST_SEED=4 ./risedev sit-test test_backfill_with_upstream_and_snapshot_read +``` + +## Backwards Compatibility tests + +This tests backwards compatibility between the earliest minor version +and latest minor version of Risingwave (e.g. 1.0.0 vs 1.1.0). + +You can run it locally with: +```bash +./risedev backwards-compat-test +``` + +In CI, you can make sure the PR runs it by adding the label `ci/run-backwards-compat-tests`. diff --git a/docs/developer-guide.md b/docs/developer-guide.md deleted file mode 100644 index 8cf5dcd4ebbaa..0000000000000 --- a/docs/developer-guide.md +++ /dev/null @@ -1,569 +0,0 @@ -# Developer guide - -This guide is intended to be used by contributors to learn about how to develop RisingWave. The instructions about how to submit code changes are included in [contributing guidelines](../CONTRIBUTING.md). - -If you have questions, you can search for existing discussions or start a new discussion in the [Discussions forum of RisingWave](https://github.com/risingwavelabs/risingwave/discussions), or ask in the RisingWave Community channel on Slack. Please use the [invitation link](https://risingwave.com/slack) to join the channel. - -To report bugs, create a [GitHub issue](https://github.com/risingwavelabs/risingwave/issues/new/choose). - - -## Table of contents - - - -- [Read the design docs](#read-the-design-docs) -- [Learn about the code structure](#learn-about-the-code-structure) -- [Set up the development environment](#set-up-the-development-environment) -- [Start and monitor a dev cluster](#start-and-monitor-a-dev-cluster) - * [Configure additional components](#configure-additional-components) - * [Configure system variables](#configure-system-variables) - * [Start the playground with RiseDev](#start-the-playground-with-risedev) - * [Start the playground with cargo](#start-the-playground-with-cargo) -- [Debug playground using vscode](#debug-playground-using-vscode) -- [Use standalone-mode](#use-standalone-mode) -- [Develop the dashboard](#develop-the-dashboard) -- [Observability components](#observability-components) - * [Cluster Control](#cluster-control) - * [Monitoring](#monitoring) - * [Tracing](#tracing) - * [Dashboard](#dashboard) - * [Logging](#logging) -- [Test your code changes](#test-your-code-changes) - * [Lint](#lint) - * [Unit tests](#unit-tests) - * [Planner tests](#planner-tests) - * [End-to-end tests](#end-to-end-tests) - * [End-to-end tests on CI](#end-to-end-tests-on-ci) - * [Fuzzing tests](#fuzzing-tests) - * [DocSlt tests](#docslt-tests) - * [Deterministic simulation tests](#deterministic-simulation-tests) -- [Miscellaneous checks](#miscellaneous-checks) -- [Update Grafana dashboard](#update-grafana-dashboard) -- [Add new files](#add-new-files) -- [Add new dependencies](#add-new-dependencies) -- [Submit PRs](#submit-prs) -- [Profiling](#benchmarking-and-profiling) -- [Understanding RisingWave Macros](#understanding-risingwave-macros) -- [CI Labels Guide](#ci-labels-guide) - -## Read the design docs - -Before you start to make code changes, ensure that you understand the design and implementation of RisingWave. We recommend that you read the design docs listed in [docs/README.md](README.md) first. - -You can also read the [crate level documentation](https://risingwavelabs.github.io/risingwave/) for implementation details. You can also run `./risedev doc` to read it locally. Note that you need to [set up the development environment](#set-up-the-development-environment) first. - -## Learn about the code structure - -- The `src` folder contains all of the kernel components, refer to [src/README.md](../src/README.md) for more details. -- The `docker` folder contains Docker files to build and start RisingWave. -- The `e2e_test` folder contains the latest end-to-end test cases. -- The `docs` folder contains the design docs. If you want to learn about how RisingWave is designed and implemented, check out the design docs here. -- The `dashboard` folder contains RisingWave dashboard. - -The [src/README.md](../src/README.md) file contains more details about Design Patterns in RisingWave. - -## Set up the development environment - -RiseDev is the development mode of RisingWave. To develop RisingWave, you need to build from the source code and run RiseDev. RiseDev can be built on macOS and Linux. It has the following dependencies: - -* Rust toolchain -* CMake -* protobuf (>= 3.12.0) -* PostgreSQL (psql) (>= 14.1) -* Tmux (>= v3.2a) -* LLVM 16 (For macOS only, to workaround some bugs in macOS toolchain. See https://github.com/risingwavelabs/risingwave/issues/6205) -* Python (>= 3.12) (Optional, only required by `embedded-python-udf` feature) - -To install the dependencies on macOS, run: - -```shell -brew install postgresql cmake protobuf tmux cyrus-sasl llvm -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -To install the dependencies on Debian-based Linux systems, run: - -```shell -sudo apt install make build-essential cmake protobuf-compiler curl postgresql-client tmux lld pkg-config libssl-dev libsasl2-dev -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -Then you'll be able to compile and start RiseDev! - -> [!NOTE] -> -> `.cargo/config.toml` contains `rustflags` configurations like `-Clink-arg` and `-Ctarget-feature`. Since it will be [merged](https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure) with `$HOME/.cargo/config.toml`, check the config files and make sure they don't conflict if you have global `rustflags` configurations for e.g. linker there. - -> [!INFO] -> -> If you want to build RisingWave with `embedded-python-udf` feature, you need to install Python 3.12. -> -> To install Python 3.12 on macOS, run: -> -> ```shell -> brew install python@3.12 -> ``` -> -> To install Python 3.12 on Debian-based Linux systems, run: -> -> ```shell -> sudo apt install software-properties-common -> sudo add-apt-repository ppa:deadsnakes/ppa -> sudo apt-get update -> sudo apt-get install python3.12 python3.12-dev -> ``` -> -> If the default `python3` version is not 3.12, please set the `PYO3_PYTHON` environment variable: -> -> ```shell -> export PYO3_PYTHON=python3.12 -> ``` - -## Start and monitor a dev cluster - -You can now build RiseDev and start a dev cluster. It is as simple as: - -```shell -./risedev d # shortcut for ./risedev dev -psql -h localhost -p 4566 -d dev -U root -``` - -If you detect memory bottlenecks while compiling, either allocate some disk space on your computer as swap memory, or lower the compilation parallelism with [`CARGO_BUILD_JOBS`](https://doc.rust-lang.org/cargo/reference/config.html#buildjobs), e.g. `CARGO_BUILD_JOBS=2`. - -The default dev cluster includes metadata-node, compute-node and frontend-node processes, and an embedded volatile in-memory state storage. No data will be persisted. This configuration is intended to make it easier to develop and debug RisingWave. - -To stop the cluster: - -```shell -./risedev k # shortcut for ./risedev kill -``` - -To view the logs: - -```shell -./risedev l # shortcut for ./risedev logs -``` - -To clean local data and logs: - -```shell -./risedev clean-data -``` - -### Configure additional components - -There are a few components that you can configure in RiseDev. - -Use the `./risedev configure` command to start the interactive configuration mode, in which you can enable and disable components. - -- Hummock (MinIO + MinIO-CLI): Enable this component to persist state data. -- Prometheus and Grafana: Enable this component to view RisingWave metrics. You can view the metrics through a built-in Grafana dashboard. -- Etcd: Enable this component if you want to persist metadata node data. -- Kafka: Enable this component if you want to create a streaming source from a Kafka topic. -- Grafana Tempo: Use this component for tracing. - -To manually add those components into the cluster, you will need to configure RiseDev to download them first. For example, - -```shell -./risedev configure enable prometheus-and-grafana # enable Prometheus and Grafana -./risedev configure enable minio # enable MinIO -``` -> [!NOTE] -> -> Enabling a component with the `./risedev configure enable` command will only download the component to your environment. To allow it to function, you must revise the corresponding configuration setting in `risedev.yml` and restart the dev cluster. - -For example, you can modify the default section to: - -```yaml - default: - - use: minio - - use: meta-node - - use: compute-node - - use: frontend - - use: prometheus - - use: grafana - - use: zookeeper - persist-data: true - - use: kafka - persist-data: true -``` - -> [!NOTE] -> -> The Kafka service depends on the ZooKeeper service. If you want to enable the Kafka component, enable the ZooKeeper component first. - -Now you can run `./risedev d` to start a new dev cluster. The new dev cluster will contain components as configured in the yaml file. RiseDev will automatically configure the components to use the available storage service and to monitor the target. - -You may also add multiple compute nodes in the cluster. The `ci-3cn-1fe` config is an example. - -### Configure system variables - -You can check `src/common/src/config.rs` to see all the configurable variables. -If additional variables are needed, -include them in the correct sections (such as `[server]` or `[storage]`) in `src/config/risingwave.toml`. - - -### Start the playground with RiseDev - -If you do not need to start a full cluster to develop, you can issue `./risedev p` to start the playground, where the metadata node, compute nodes and frontend nodes are running in the same process. Logs are printed to stdout instead of separate log files. - -```shell -./risedev p # shortcut for ./risedev playground -``` - -For more information, refer to `README.md` under `src/risedevtool`. - -### Start the playground with cargo - -To start the playground (all-in-one process) from IDE or command line, you can use: - -```shell -cargo run --bin risingwave -- playground -``` - -Then, connect to the playground instance via: - -```shell -psql -h localhost -p 4566 -d dev -U root -``` - -## Debug playground using vscode - -To step through risingwave locally with a debugger you can use the `launch.json` and the `tasks.json` provided in `vscode_suggestions`. After adding these files to your local `.vscode` folder you can debug and set breakpoints by launching `Launch 'risingwave p' debug`. - -## Use standalone-mode - -Please refer to [README](../src/cmd_all/src/README.md) for more details. - -## Develop the dashboard - -Currently, RisingWave has two versions of dashboards. You can use RiseDev config to select which version to use. - -The dashboard will be available at `http://127.0.0.1:5691/` on meta node. - -The development instructions for dashboard are available [here](../dashboard/README.md). - -## Observability components - -RiseDev supports several observability components. - -### Cluster Control - -`risectl` is the tool for providing internal access to the RisingWave cluster. See - -``` -cargo run --bin risectl -- --help -``` - -... or - -``` -./risedev ctl --help -``` - -for more information. - -### Monitoring - -Uncomment `grafana` and `prometheus` lines in `risedev.yml` to enable Grafana and Prometheus services. - -### Tracing - -Compute nodes support streaming tracing. Tracing is not enabled by default. You need to -use `./risedev configure` to download the tracing components first. After that, you will need to uncomment `tempo` -service in `risedev.yml` and start a new dev cluster to allow the components to work. - -Traces are visualized in Grafana. You may also want to uncomment `grafana` service in `risedev.yml` to enable it. - -### Dashboard - -You may use RisingWave Dashboard to see actors in the system. It will be started along with meta node. - -### Logging - -The Rust components use `tokio-tracing` to handle both logging and tracing. The default log level is set as: - -* Third-party libraries: warn -* Other libraries: debug - -If you need to override the default log levels, launch RisingWave with the environment variable `RUST_LOG` set as described [here](https://docs.rs/tracing-subscriber/0.3/tracing_subscriber/filter/struct.EnvFilter.html). - -There're also some logs designated for debugging purposes with target names starting with `events::`. -For example, by setting `RUST_LOG=events::stream::message::chunk=trace`, all chunk messages will be logged as it passes through the executors in the streaming engine. Search in the codebase to find more of them. - - -## Test your code changes - -Before you submit a PR, fully test the code changes and perform necessary checks. - -The RisingWave project enforces several checks in CI. Every time the code is modified, you need to perform the checks and ensure they pass. - -### Lint - -RisingWave requires all code to pass fmt, clippy, sort and hakari checks. Run the following commands to install test tools and perform these checks. - -```shell -./risedev install-tools # Install required tools for running unit tests -./risedev c # Run all checks. Shortcut for ./risedev check -``` - -### Unit tests - -RiseDev runs unit tests with cargo-nextest. To run unit tests: - -```shell -./risedev install-tools # Install required tools for running unit tests -./risedev test # Run unit tests -``` - -If you want to see the coverage report, run this command: - -```shell -./risedev test-cov -``` - -Some unit tests will not work if the `/tmp` directory is on a TmpFS file system: these unit tests will fail with this -error message: `Attempting to create cache file on a TmpFS file system. TmpFS cannot be used because it does not support Direct IO.`. -If this happens you can override the use of `/tmp` by setting the environment variable `RISINGWAVE_TEST_DIR` to a -directory that is on a non-TmpFS filesystem, the unit tests will then place temporary files under your specified path. - -### Planner tests - -RisingWave's SQL frontend has SQL planner tests. For more information, see [Planner Test Guide](../src/frontend/planner_test/README.md). - -### End-to-end tests - -Use [sqllogictest-rs](https://github.com/risinglightdb/sqllogictest-rs) to run RisingWave e2e tests. - -sqllogictest installation is included when you install test tools with the `./risedev install-tools` command. You may also install it with: - -```shell -cargo install sqllogictest-bin --locked -``` - -Before running end-to-end tests, you will need to start a full cluster first: - -```shell -./risedev d -``` - -Then to run the end-to-end tests, you can use one of the following commands according to which component you are developing: - -```shell -# run all streaming tests -./risedev slt-streaming -p 4566 -d dev -j 1 -# run all batch tests -./risedev slt-batch -p 4566 -d dev -j 1 -# run both -./risedev slt-all -p 4566 -d dev -j 1 -``` - -> [!NOTE] -> -> Use `-j 1` to create a separate database for each test case, which can ensure that previous test case failure won't affect other tests due to table cleanups. - -Alternatively, you can also run some specific tests: - -```shell -# run a single test -./risedev slt -p 4566 -d dev './e2e_test/path/to/file.slt' -# run all tests under a directory (including subdirectories) -./risedev slt -p 4566 -d dev './e2e_test/path/to/directory/**/*.slt' -``` - -After running e2e tests, you may kill the cluster and clean data. - -```shell -./risedev k # shortcut for ./risedev kill -./risedev clean-data -``` - -RisingWave's codebase is constantly changing. The persistent data might not be stable. In case of unexpected decode errors, try `./risedev clean-data` first. - -### End-to-end tests on CI - -Basically, CI is using the following two configurations to run the full e2e test suite: - -```shell -./risedev dev ci-3cn-1fe -``` - -You can adjust the environment variable to enable some specific code to make all e2e tests pass. Refer to GitHub Action workflow for more information. - -### Fuzzing tests - -#### SqlSmith - -Currently, SqlSmith supports for e2e and frontend fuzzing. Take a look at [Fuzzing tests](../src/tests/sqlsmith/README.md) for more details on running it locally. - -### DocSlt tests - -As introduced in [#5117](https://github.com/risingwavelabs/risingwave/issues/5117), DocSlt tool allows you to write SQL examples in sqllogictest syntax in Rust doc comments. After adding or modifying any such SQL examples, you should run the following commands to generate and run e2e tests for them. - -```shell -# generate e2e tests from doc comments for all default packages -./risedev docslt -# or, generate for only modified package -./risedev docslt -p risingwave_expr - -# run all generated e2e tests -./risedev slt-generated -p 4566 -d dev -# or, run only some of them -./risedev slt -p 4566 -d dev './e2e_test/generated/docslt/risingwave_expr/**/*.slt' -``` - -These will be run on CI as well. - -### Deterministic simulation tests - -Deterministic simulation is a powerful tool to efficiently search bugs and reliably reproduce them. -In case you are not familiar with this technique, here is a [talk](https://www.youtube.com/watch?v=4fFDFbi3toc) and a [blog post](https://sled.rs/simulation.html) for brief introduction. - -In RisingWave, deterministic simulation is supported in both unit test and end-to-end test. You can run them using the following commands: - -```sh -# run deterministic unit test -./risedev stest -# run deterministic end-to-end test -./risedev sslt -- './e2e_test/path/to/directory/**/*.slt' -``` - -When your program panics, the simulator will print the random seed of this run: - -```sh -thread '' panicked at '...', -note: run with `MADSIM_TEST_SEED=1` environment variable to reproduce this error -``` - -Then you can reproduce the bug with the given seed: - -```sh -# set the random seed to reproduce a run -MADSIM_TEST_SEED=1 RUST_LOG=info ./risedev sslt -- './e2e_test/path/to/directory/**/*.slt' -``` - -More advanced usages are listed below: - -```sh -# run multiple times with different seeds to test reliability -# it's recommended to build in release mode for a fast run -MADSIM_TEST_NUM=100 ./risedev sslt --release -- './e2e_test/path/to/directory/**/*.slt' - -# configure cluster nodes (by default: 2fe+3cn) -./risedev sslt -- --compute-nodes 2 './e2e_test/path/to/directory/**/*.slt' - -# inject failures to test fault recovery -./risedev sslt -- --kill-meta --etcd-timeout-rate=0.01 './e2e_test/path/to/directory/**/*.slt' - -# see more usages -./risedev sslt -- --help -``` - -Deterministic test is included in CI as well. See [CI script](../ci/scripts/deterministic-e2e-test.sh) for details. - -### Deterministic Simulation Integration tests - -To run these tests: -```shell -./risedev sit-test -``` - -Sometimes in CI you may see a backtrace, followed by an error message with a `MADSIM_TEST_SEED`: -```shell - 161: madsim::sim::task::Executor::block_on - at /risingwave/.cargo/registry/src/index.crates.io-6f17d22bba15001f/madsim-0.2.22/src/sim/task/mod.rs:238:13 - 162: madsim::sim::runtime::Runtime::block_on - at /risingwave/.cargo/registry/src/index.crates.io-6f17d22bba15001f/madsim-0.2.22/src/sim/runtime/mod.rs:126:9 - 163: madsim::sim::runtime::builder::Builder::run::{{closure}}::{{closure}}::{{closure}} - at /risingwave/.cargo/registry/src/index.crates.io-6f17d22bba15001f/madsim-0.2.22/src/sim/runtime/builder.rs:128:35 -note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. -context: node=6 "compute-1", task=2237 (spawned at /risingwave/src/stream/src/task/stream_manager.rs:689:34) -note: run with `MADSIM_TEST_SEED=2` environment variable to reproduce this error -``` - -You may use that to reproduce it in your local environment. For example: -```shell -MADSIM_TEST_SEED=4 ./risedev sit-test test_backfill_with_upstream_and_snapshot_read -``` - -### Backwards Compatibility tests - -This tests backwards compatibility between the earliest minor version -and latest minor version of Risingwave (e.g. 1.0.0 vs 1.1.0). - -You can run it locally with: -```bash -./risedev backwards-compat-test -``` - -In CI, you can make sure the PR runs it by adding the label `ci/run-backwards-compat-tests`. - -## Miscellaneous checks - -For shell code, please run: - -```shell -brew install shellcheck -shellcheck -``` - -For Protobufs, we rely on [buf](https://docs.buf.build/installation) for code formatting and linting. Please check out their documents for installation. To check if you violate the rules, please run the commands: - -```shell -buf format -d --exit-code -buf lint -``` - -## Update Grafana dashboard - -See [README](../grafana/README.md) for more information. - -## Add new files - -We use [skywalking-eyes](https://github.com/apache/skywalking-eyes) to manage license headers. -If you added new files, please follow the installation guide and run: - -```shell -license-eye -c .licenserc.yaml header fix -``` - -## Add new dependencies - -`./risedev check-hakari`: To avoid rebuild some common dependencies across different crates in workspace, use -[cargo-hakari](https://docs.rs/cargo-hakari/latest/cargo_hakari/) to ensure all dependencies -are built with the same feature set across workspace. You'll need to run `cargo hakari generate` -after deps get updated. - -`./risedev check-udeps`: Use [cargo-udeps](https://github.com/est31/cargo-udeps) to find unused dependencies in workspace. - -`./risedev check-dep-sort`: Use [cargo-sort](https://crates.io/crates/cargo-sort) to ensure all deps are get sorted. - -## Submit PRs - -Instructions about submitting PRs are included in the [contribution guidelines](../CONTRIBUTING.md). - -## Benchmarking and Profiling - -- [CPU Profiling Guide](./cpu-profiling.md) -- [Memory (Heap) Profiling Guide](./memory-profiling.md) -- [Microbench Guide](./microbenchmarks.md) - -## CI Labels Guide - -- `[ci/run-xxx ...]`: Run additional steps indicated by `ci/run-xxx` in your PR. -- `ci/skip-ci` + `[ci/run-xxx ...]` : Skip steps except for those indicated by `ci/run-xxx` in your **DRAFT PR.** -- `ci/run-main-cron`: Run full `main-cron`. -- `ci/run-main-cron` + `ci/main-cron/skip-ci` + `[ci/run-xxx …]` : Run specific steps indicated by `ci/run-xxx` - from the `main-cron` workflow, in your PR. Can use to verify some `main-cron` fix works as expected. -- To reference `[ci/run-xxx ...]` labels, you may look at steps from `pull-request.yml` and `main-cron.yml`. -- **Be sure to add all the dependencies.** - - For example to run `e2e-test` for `main-cron` in your pull request: - 1. Add `ci/run-build`, `ci/run-build-other`, `ci/run-docslt` . - These correspond to its `depends` field in `pull-request.yml` and `main-cron.yml` . - 2. Add `ci/run-e2e-test` to run the step as well. - 3. Add `ci/run-main-cron` to run `main-cron` workflow in your pull request, - 4. Add `ci/main-cron/skip-ci` to skip all other steps which were not selected with `ci/run-xxx`. \ No newline at end of file diff --git a/docs/rustdoc/index.md b/docs/rustdoc/index.md index d8fe56471edb8..cfb74b8055b8a 100644 --- a/docs/rustdoc/index.md +++ b/docs/rustdoc/index.md @@ -2,7 +2,9 @@ Welcome to an overview of the developer documentations of RisingWave! -## Design Docs +## Developer Docs + +To learn how to develop RisingWave, see the [RisingWave Developer Guide](https://risingwavelabs.github.io/risingwave/). The [design docs](https://github.com/risingwavelabs/risingwave/blob/main/docs/README.md) covers some high-level ideas of how we built RisingWave. diff --git a/e2e_test/README.md b/e2e_test/README.md index 198948e8eee15..333b3ef598239 100644 --- a/e2e_test/README.md +++ b/e2e_test/README.md @@ -4,16 +4,16 @@ This folder contains sqllogictest source files for e2e. It is running on CI for e2e test should drop table if created one to avoid table exist conflict (Currently all tests are running in a single database). -## How to write e2e tests +## How to write and run e2e tests -Refer to Sqllogictest [Doc](https://www.sqlite.org/sqllogictest/doc/trunk/about.wiki). - -## How to run e2e tests - -Refer to risingwave [developer guide](../docs/developer-guide.md#end-to-end-tests). +Refer to the [RisingWave Developer Guide](https://risingwavelabs.github.io/risingwave/tests/intro.html#end-to-end-tests). > [!NOTE] > > Usually you will just need to run either batch tests or streaming tests. Other tests may need to be run under some specific settings, e.g., ddl tests need to be run on a fresh instance, and database tests need to first create a database and then connect to that database to run tests. > > You will never want to run all tests using `./e2e_test/**/*.slt`. You may refer to the [ci script](../ci/scripts/run-e2e-test.sh) to see how to run all tests. + +## How to test connectors + +See the [connector development guide](http://risingwavelabs.github.io/risingwave/connector/intro.html#end-to-end-tests). diff --git a/e2e_test/backfill/sink/create_sink.slt b/e2e_test/backfill/sink/create_sink.slt index bc9fba04da5c8..016e3bcb2049b 100644 --- a/e2e_test/backfill/sink/create_sink.slt +++ b/e2e_test/backfill/sink/create_sink.slt @@ -5,9 +5,9 @@ statement ok create table t (v1 int); statement ok -SET STREAMING_RATE_LIMIT = 1000; +SET STREAMING_RATE_LIMIT = 500; -# Should finish in 10s +# Should finish in 20s statement ok insert into t select * from generate_series(1, 10000); @@ -20,7 +20,7 @@ from t x join t y on x.v1 = y.v1 with ( connector='kafka', - properties.bootstrap.server='localhost:29092', + properties.bootstrap.server='message_queue:29092', topic='s_kafka', primary_key='v1', allow.auto.create.topics=true, diff --git a/e2e_test/backfill/sink/different_pk_and_dist_key.slt b/e2e_test/backfill/sink/different_pk_and_dist_key.slt new file mode 100644 index 0000000000000..bc8256b28e62a --- /dev/null +++ b/e2e_test/backfill/sink/different_pk_and_dist_key.slt @@ -0,0 +1,49 @@ +statement ok +create table t(v1 int, v2 int primary key, v3 int); + +statement ok +create table t2(v1 int, v2 int primary key, v3 int); + +# Let snapshot side pk >= upstream side +statement ok +insert into t select 50000 + generate_series, 60000 + generate_series, 70000 + generate_series from generate_series(1, 10000); + +statement ok +insert into t2 select 50000 + generate_series, 60000 + generate_series, 70000 + generate_series from generate_series(1, 10000); + +statement ok +flush; + +statement ok +create materialized view m1 as select t.v1, t.v2, t.v3 from t join t2 using(v1); + +statement ok +set streaming_rate_limit = 1; + +statement ok +set background_ddl = true; + +statement ok +create sink s1 as select t.v1, t.v2, t.v3 from m1 join t using(v3) with (connector = 'blackhole'); + +# Let snapshot side pk >= upstream side +statement ok +insert into t select 10000 + generate_series, 20000 + generate_series, 30000 + generate_series from generate_series(1, 10000); + +statement ok +insert into t2 select 10000 + generate_series, 20000 + generate_series, 30000 + generate_series from generate_series(1, 10000); + +statement ok +flush; + +statement ok +drop sink s1; + +statement ok +drop materialized view m1; + +statement ok +drop table t; + +statement ok +drop table t2; \ No newline at end of file diff --git a/backwards-compat-tests/README.md b/e2e_test/backwards-compat-tests/README.md similarity index 100% rename from backwards-compat-tests/README.md rename to e2e_test/backwards-compat-tests/README.md diff --git a/backwards-compat-tests/scripts/run_local.sh b/e2e_test/backwards-compat-tests/scripts/run_local.sh similarity index 59% rename from backwards-compat-tests/scripts/run_local.sh rename to e2e_test/backwards-compat-tests/scripts/run_local.sh index 76b1a4f333458..7ac952e6d1c4a 100755 --- a/backwards-compat-tests/scripts/run_local.sh +++ b/e2e_test/backwards-compat-tests/scripts/run_local.sh @@ -10,11 +10,14 @@ on_exit() { trap on_exit EXIT -source backwards-compat-tests/scripts/utils.sh +source e2e_test/backwards-compat-tests/scripts/utils.sh configure_rw() { -echo "--- Setting up cluster config" -cat < risedev-profiles.user.yml + VERSION="$1" + + echo "--- Setting up cluster config" + if version_le "$VERSION" "1.9.0"; then + cat < risedev-profiles.user.yml full-without-monitoring: steps: - use: minio @@ -23,16 +26,34 @@ full-without-monitoring: - use: compute-node - use: frontend - use: compactor - - use: zookeeper - use: kafka + user-managed: true + address: message_queue + port: 29092 +EOF + else + cat < risedev-profiles.user.yml + full-without-monitoring: + steps: + - use: minio + - use: etcd + - use: meta-node + meta-backend: etcd + - use: compute-node + - use: frontend + - use: compactor + - use: kafka + user-managed: true + address: message_queue + port: 29092 EOF + fi cat < risedev-components.user.env RISEDEV_CONFIGURED=false ENABLE_MINIO=true ENABLE_ETCD=true -ENABLE_KAFKA=true # Fetch risingwave binary from release. ENABLE_BUILD_RUST=true @@ -57,12 +78,12 @@ main() { set -euo pipefail get_rw_versions setup_old_cluster - configure_rw + configure_rw "$OLD_VERSION" seed_old_cluster "$OLD_VERSION" setup_new_cluster - configure_rw + configure_rw "99.99.99" validate_new_cluster "$NEW_VERSION" } -main \ No newline at end of file +main diff --git a/backwards-compat-tests/scripts/utils.sh b/e2e_test/backwards-compat-tests/scripts/utils.sh similarity index 86% rename from backwards-compat-tests/scripts/utils.sh rename to e2e_test/backwards-compat-tests/scripts/utils.sh index 0fd3e3908ca6e..8f41dad0860f1 100644 --- a/backwards-compat-tests/scripts/utils.sh +++ b/e2e_test/backwards-compat-tests/scripts/utils.sh @@ -18,36 +18,9 @@ RECOVERY_DURATION=20 # Setup test directory -TEST_DIR=.risingwave/backwards-compat-tests/ -KAFKA_PATH=.risingwave/bin/kafka +TEST_DIR=.risingwave/e2e_test/backwards-compat-tests/ mkdir -p $TEST_DIR -cp -r backwards-compat-tests/slt/* $TEST_DIR - -wait_kafka_exit() { - # Follow kafka-server-stop.sh - while [[ -n "$(ps ax | grep ' kafka\.Kafka ' | grep java | grep -v grep | awk '{print $1}')" ]]; do - echo "Waiting for kafka to exit" - sleep 1 - done -} - -wait_zookeeper_exit() { - # Follow zookeeper-server-stop.sh - while [[ -n "$(ps ax | grep java | grep -i QuorumPeerMain | grep -v grep | awk '{print $1}')" ]]; do - echo "Waiting for zookeeper to exit" - sleep 1 - done -} - -kill_kafka() { - $KAFKA_PATH/bin/kafka-server-stop.sh - wait_kafka_exit -} - -kill_zookeeper() { - $KAFKA_PATH/bin/zookeeper-server-stop.sh - wait_zookeeper_exit -} +cp -r e2e_test/backwards-compat-tests/slt/* $TEST_DIR wait_for_process() { process_name="$1" @@ -77,24 +50,9 @@ kill_cluster() { # Kill other components $TMUX list-windows -t risedev -F "#{window_name} #{pane_id}" | - grep -v 'kafka' | - grep -v 'zookeeper' | awk '{ print $2 }' | xargs -I {} $TMUX send-keys -t {} C-c C-d - set +e - if [[ -n $($TMUX list-windows -t risedev | grep kafka) ]]; then - echo "kill kafka" - kill_kafka - - echo "kill zookeeper" - kill_zookeeper - - # Kill their tmux sessions - $TMUX list-windows -t risedev -F "#{pane_id}" | xargs -I {} $TMUX send-keys -t {} C-c C-d - fi - set -e - $TMUX kill-server test $? -eq 0 || { echo "Failed to stop all RiseDev components." @@ -120,18 +78,16 @@ check_version() { } create_kafka_topic() { - "$KAFKA_PATH"/bin/kafka-topics.sh \ - --create \ - --topic backwards_compat_test_kafka_source --bootstrap-server localhost:29092 + RPK_BROKERS=message_queue:29092 \ + rpk topic create backwards_compat_test_kafka_source } insert_json_kafka() { local JSON=$1 - echo "$JSON" | "$KAFKA_PATH"/bin/kafka-console-producer.sh \ - --topic backwards_compat_test_kafka_source \ - --bootstrap-server localhost:29092 \ - --property "parse.key=true" \ - --property "key.separator=," + + echo "$JSON" | \ + RPK_BROKERS=message_queue:29092 \ + rpk topic produce backwards_compat_test_kafka_source -f "%k,%v" } seed_json_kafka() { diff --git a/backwards-compat-tests/slt/basic/seed.slt b/e2e_test/backwards-compat-tests/slt/basic/seed.slt similarity index 100% rename from backwards-compat-tests/slt/basic/seed.slt rename to e2e_test/backwards-compat-tests/slt/basic/seed.slt diff --git a/backwards-compat-tests/slt/basic/validate_original.slt b/e2e_test/backwards-compat-tests/slt/basic/validate_original.slt similarity index 100% rename from backwards-compat-tests/slt/basic/validate_original.slt rename to e2e_test/backwards-compat-tests/slt/basic/validate_original.slt diff --git a/backwards-compat-tests/slt/basic/validate_restart.slt b/e2e_test/backwards-compat-tests/slt/basic/validate_restart.slt similarity index 100% rename from backwards-compat-tests/slt/basic/validate_restart.slt rename to e2e_test/backwards-compat-tests/slt/basic/validate_restart.slt diff --git a/backwards-compat-tests/slt/kafka/invalid_options/seed.slt b/e2e_test/backwards-compat-tests/slt/kafka/invalid_options/seed.slt similarity index 92% rename from backwards-compat-tests/slt/kafka/invalid_options/seed.slt rename to e2e_test/backwards-compat-tests/slt/kafka/invalid_options/seed.slt index 6021cf1253b8e..2fef943c8ab49 100644 --- a/backwards-compat-tests/slt/kafka/invalid_options/seed.slt +++ b/e2e_test/backwards-compat-tests/slt/kafka/invalid_options/seed.slt @@ -14,7 +14,7 @@ CREATE SOURCE IF NOT EXISTS kafka_source_with_invalid_option WITH ( connector='kafka', topic='backwards_compat_test_kafka_source', - properties.bootstrap.server='localhost:29092', + properties.bootstrap.server='message_queue:29092', scan.startup.mode='earliest', invalid_option='oops' ) FORMAT PLAIN ENCODE JSON; diff --git a/backwards-compat-tests/slt/kafka/invalid_options/validate_original.slt b/e2e_test/backwards-compat-tests/slt/kafka/invalid_options/validate_original.slt similarity index 100% rename from backwards-compat-tests/slt/kafka/invalid_options/validate_original.slt rename to e2e_test/backwards-compat-tests/slt/kafka/invalid_options/validate_original.slt diff --git a/backwards-compat-tests/slt/kafka/invalid_options/validate_restart.slt b/e2e_test/backwards-compat-tests/slt/kafka/invalid_options/validate_restart.slt similarity index 100% rename from backwards-compat-tests/slt/kafka/invalid_options/validate_restart.slt rename to e2e_test/backwards-compat-tests/slt/kafka/invalid_options/validate_restart.slt diff --git a/backwards-compat-tests/slt/kafka/seed.slt b/e2e_test/backwards-compat-tests/slt/kafka/seed.slt similarity index 87% rename from backwards-compat-tests/slt/kafka/seed.slt rename to e2e_test/backwards-compat-tests/slt/kafka/seed.slt index 3840ce0c96b15..aa2a196694e4d 100644 --- a/backwards-compat-tests/slt/kafka/seed.slt +++ b/e2e_test/backwards-compat-tests/slt/kafka/seed.slt @@ -11,7 +11,7 @@ CREATE SOURCE IF NOT EXISTS kafka_source WITH ( connector='kafka', topic='backwards_compat_test_kafka_source', - properties.bootstrap.server='localhost:29092', + properties.bootstrap.server='message_queue:29092', scan.startup.mode='earliest', ) FORMAT PLAIN ENCODE JSON; diff --git a/backwards-compat-tests/slt/kafka/upsert/deprecate_upsert.slt b/e2e_test/backwards-compat-tests/slt/kafka/upsert/deprecate_upsert.slt similarity index 76% rename from backwards-compat-tests/slt/kafka/upsert/deprecate_upsert.slt rename to e2e_test/backwards-compat-tests/slt/kafka/upsert/deprecate_upsert.slt index 55cfce886455d..57f1e2d27391b 100644 --- a/backwards-compat-tests/slt/kafka/upsert/deprecate_upsert.slt +++ b/e2e_test/backwards-compat-tests/slt/kafka/upsert/deprecate_upsert.slt @@ -11,6 +11,6 @@ CREATE TABLE IF NOT EXISTS kafka_table WITH ( connector='kafka', topic='backwards_compat_test_kafka_source', - properties.bootstrap.server='localhost:29092', + properties.bootstrap.server='message_queue:29092', scan.startup.mode='earliest', -) FORMAT UPSERT ENCODE JSON; \ No newline at end of file +) FORMAT UPSERT ENCODE JSON; diff --git a/backwards-compat-tests/slt/kafka/upsert/include_key_as.slt b/e2e_test/backwards-compat-tests/slt/kafka/upsert/include_key_as.slt similarity index 78% rename from backwards-compat-tests/slt/kafka/upsert/include_key_as.slt rename to e2e_test/backwards-compat-tests/slt/kafka/upsert/include_key_as.slt index 36ef426574223..37d41e195ee05 100644 --- a/backwards-compat-tests/slt/kafka/upsert/include_key_as.slt +++ b/e2e_test/backwards-compat-tests/slt/kafka/upsert/include_key_as.slt @@ -13,6 +13,6 @@ INCLUDE key as _rw_key WITH ( connector='kafka', topic='backwards_compat_test_kafka_source', - properties.bootstrap.server='localhost:29092', + properties.bootstrap.server='message_queue:29092', scan.startup.mode='earliest', -) FORMAT UPSERT ENCODE JSON; \ No newline at end of file +) FORMAT UPSERT ENCODE JSON; diff --git a/backwards-compat-tests/slt/kafka/validate_original.slt b/e2e_test/backwards-compat-tests/slt/kafka/validate_original.slt similarity index 100% rename from backwards-compat-tests/slt/kafka/validate_original.slt rename to e2e_test/backwards-compat-tests/slt/kafka/validate_original.slt diff --git a/backwards-compat-tests/slt/kafka/validate_restart.slt b/e2e_test/backwards-compat-tests/slt/kafka/validate_restart.slt similarity index 100% rename from backwards-compat-tests/slt/kafka/validate_restart.slt rename to e2e_test/backwards-compat-tests/slt/kafka/validate_restart.slt diff --git a/backwards-compat-tests/slt/nexmark-backwards-compat/delete.slt.part b/e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/delete.slt.part similarity index 100% rename from backwards-compat-tests/slt/nexmark-backwards-compat/delete.slt.part rename to e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/delete.slt.part diff --git a/backwards-compat-tests/slt/nexmark-backwards-compat/insert.slt b/e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/insert.slt similarity index 100% rename from backwards-compat-tests/slt/nexmark-backwards-compat/insert.slt rename to e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/insert.slt diff --git a/backwards-compat-tests/slt/nexmark-backwards-compat/seed.slt b/e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/seed.slt similarity index 100% rename from backwards-compat-tests/slt/nexmark-backwards-compat/seed.slt rename to e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/seed.slt diff --git a/backwards-compat-tests/slt/nexmark-backwards-compat/validate_original.slt b/e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/validate_original.slt similarity index 100% rename from backwards-compat-tests/slt/nexmark-backwards-compat/validate_original.slt rename to e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/validate_original.slt diff --git a/backwards-compat-tests/slt/nexmark-backwards-compat/validate_restart.slt b/e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/validate_restart.slt similarity index 100% rename from backwards-compat-tests/slt/nexmark-backwards-compat/validate_restart.slt rename to e2e_test/backwards-compat-tests/slt/nexmark-backwards-compat/validate_restart.slt diff --git a/backwards-compat-tests/slt/tpch-backwards-compat/delete.slt.part b/e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/delete.slt.part similarity index 100% rename from backwards-compat-tests/slt/tpch-backwards-compat/delete.slt.part rename to e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/delete.slt.part diff --git a/backwards-compat-tests/slt/tpch-backwards-compat/insert.slt.part b/e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/insert.slt.part similarity index 100% rename from backwards-compat-tests/slt/tpch-backwards-compat/insert.slt.part rename to e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/insert.slt.part diff --git a/backwards-compat-tests/slt/tpch-backwards-compat/seed.slt b/e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/seed.slt similarity index 100% rename from backwards-compat-tests/slt/tpch-backwards-compat/seed.slt rename to e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/seed.slt diff --git a/backwards-compat-tests/slt/tpch-backwards-compat/validate_original.slt b/e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/validate_original.slt similarity index 100% rename from backwards-compat-tests/slt/tpch-backwards-compat/validate_original.slt rename to e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/validate_original.slt diff --git a/backwards-compat-tests/slt/tpch-backwards-compat/validate_restart.slt b/e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/validate_restart.slt similarity index 100% rename from backwards-compat-tests/slt/tpch-backwards-compat/validate_restart.slt rename to e2e_test/backwards-compat-tests/slt/tpch-backwards-compat/validate_restart.slt diff --git a/e2e_test/batch/catalog/has_privilege.slt.part b/e2e_test/batch/catalog/has_privilege.slt.part new file mode 100644 index 0000000000000..a742db0c51d6f --- /dev/null +++ b/e2e_test/batch/catalog/has_privilege.slt.part @@ -0,0 +1,264 @@ +statement ok +CREATE USER test_user; + +statement ok +CREATE SCHEMA test_schema; + +statement ok +CREATE TABLE foo (id INT, name VARCHAR); + +statement ok +CREATE VIEW foo_view AS SELECT * FROM foo; + +statement ok +CREATE INDEX foo_index ON foo(id); + +statement ok +CREATE MATERIALIZED VIEW foo_mv AS SELECT * FROM foo; + +statement ok +CREATE SOURCE foo_source (a int, b int) with ( + connector = 'datagen', + datagen.rows.per.second = '1', + datagen.split.num = '1' +); + +statement ok +CREATE TABLE bar (id INT); + +statement ok +GRANT ALL PRIVILEGES ON foo TO test_user GRANTED BY root; + +statement ok +GRANT INSERT ON bar TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT INSERT ON foo_view TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT SELECT ON ALL TABLES IN SCHEMA public TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT SELECT ON ALL MATERIALIZED VIEWS IN SCHEMA public TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT SELECT ON ALL SOURCES IN SCHEMA public TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT CREATE ON SCHEMA test_schema TO test_user; + +query error table not found: bar_err +GRANT INSERT ON bar_err TO test_user WITH GRANT OPTION GRANTED BY root; + +query error Invalid parameter user: User test_user_err not found +SELECT has_table_privilege('test_user_err', 'foo', 'SELECT'); + +query error Invalid parameter name: class not found: foo_err +SELECT has_table_privilege('test_user', 'foo_err', 'SELECT'); + +query error Invalid parameter privilege: unrecognized privilege type: "SELE CT" +SELECT has_table_privilege('test_user', 'foo', 'SELE CT'); + +query error Invalid parameter privilege: unrecognized privilege type: "SELECT INSERT" +SELECT has_table_privilege('test_user', 'foo', 'SELECT INSERT'); + +query error Invalid parameter privilege +SELECT has_table_privilege('test_user', 'foo', 'SELECT, INSERT WITH GRANT OPTION'); + +query error Invalid parameter user: User test_user_err not found +SELECT has_schema_privilege('test_user_err', 'test_schema', 'CREATE'); + +query error Invalid parameter schema: schema not found: test_schema_err +SELECT has_schema_privilege('test_user', 'test_schema_err', 'CREATE'); + +query error Invalid parameter privilege: unrecognized privilege type: "INSERT" +SELECT has_schema_privilege('test_user', 'test_schema', 'INSERT'); + +query error Invalid parameter privilege: unrecognized privilege type: "DELETE" +SELECT has_any_column_privilege('test_user', 'foo_mv'::regclass, 'DELETE'); + +query I +SELECT has_table_privilege('test_user', 'foo', 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'INSERT WITH GRANT OPTION'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo', 'INSERT, SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'DELETE, INSERT, SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'DELETE WITH GRANT OPTION, INSERT, SELECT WITH GRANT OPTION'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_view'::regclass, 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_view'::regclass, 'UPDATE'); +---- +f + +query I +SELECT has_any_column_privilege('test_user', 'foo_view'::regclass, 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_mv', 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_mv'::regclass, 'SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_any_column_privilege('test_user', 'foo_mv'::regclass, 'SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_mv', 'INSERT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_source'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_source', 'INSERT'); +---- +f + +# Indexes are granted by `GRANT SELECT ON ALL MATERIALIZED VIEWS` +query I +SELECT has_table_privilege('test_user', 'foo_index'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_index', 'INSERT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'bar', 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('bar', 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('bar'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('bar'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'bar', 'UPDATE'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'bar'::regclass, 'INSERT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_schema_privilege('public', 'USAGE'); +---- +t + +query I +SELECT has_schema_privilege('test_user', 'test_schema', 'USAGE'); +---- +f + +query I +SELECT has_schema_privilege('test_user', 'test_schema', 'CREATE'); +---- +t + +statement ok +REVOKE SELECT ON ALL TABLES IN SCHEMA public FROM test_user GRANTED BY root; + +query I +SELECT has_table_privilege('test_user', 'bar'::regclass, 'SELECT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'SELECT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'INSERT'); +---- +t + +statement ok +REVOKE INSERT ON foo_view FROM test_user GRANTED BY root; + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'INSERT'); +---- +f + +statement ok +DROP SOURCE foo_source; + +statement ok +DROP MATERIALIZED VIEW foo_mv; + +statement ok +DROP INDEX foo_index; + +statement ok +DROP VIEW foo_view; + +statement ok +DROP TABLE foo; + +statement ok +DROP TABLE bar; + +statement ok +DROP SCHEMA test_schema; + +statement ok +DROP USER test_user; diff --git a/e2e_test/batch/catalog/pg_attribute.slt.part b/e2e_test/batch/catalog/pg_attribute.slt.part index 4c8570ce2f474..2615466685a3a 100644 --- a/e2e_test/batch/catalog/pg_attribute.slt.part +++ b/e2e_test/batch/catalog/pg_attribute.slt.part @@ -42,5 +42,10 @@ tmp_idx id1 {2,1,3,5} tmp_idx id2 {2,1,3,5} tmp_idx id3 {2,1,3,5} +query T +select attoptions from pg_catalog.pg_attribute LIMIT 1; +---- +NULL + statement ok drop table tmp; diff --git a/e2e_test/batch/catalog/pg_class.slt.part b/e2e_test/batch/catalog/pg_class.slt.part index ffb53e32d66b5..a6fe4a1257122 100644 --- a/e2e_test/batch/catalog/pg_class.slt.part +++ b/e2e_test/batch/catalog/pg_class.slt.part @@ -20,4 +20,4 @@ SELECT oid,relname,relowner,relkind FROM pg_catalog.pg_class ORDER BY oid limit query ITIT SELECT oid,relname,relowner,relkind FROM pg_catalog.pg_class WHERE oid = 'pg_namespace'::regclass; ---- -2147478671 pg_namespace 1 v +2147478670 pg_namespace 1 v diff --git a/e2e_test/batch/catalog/pg_indexes.slt.part b/e2e_test/batch/catalog/pg_indexes.slt.part index 634739406e917..c8e8ef6267037 100644 --- a/e2e_test/batch/catalog/pg_indexes.slt.part +++ b/e2e_test/batch/catalog/pg_indexes.slt.part @@ -1,5 +1,5 @@ statement ok -create table t(a int, b int); +create table t(a int primary key, b int); statement ok create index idx1 on t(a); @@ -12,6 +12,7 @@ select schemaname, tablename, indexname, tablespace, indexdef from pg_catalog.pg ---- public t idx1 NULL CREATE INDEX idx1 ON t(a) public t idx2 NULL CREATE INDEX idx2 ON t(b) +public t t_pkey NULL (empty) statement ok drop table t; diff --git a/e2e_test/batch/catalog/pg_settings.slt.part b/e2e_test/batch/catalog/pg_settings.slt.part index d9af757ba4c36..f405cc71c2c0d 100644 --- a/e2e_test/batch/catalog/pg_settings.slt.part +++ b/e2e_test/batch/catalog/pg_settings.slt.part @@ -7,11 +7,13 @@ internal data_directory internal parallel_compact_size_mb internal sstable_size_mb internal state_store +internal use_new_object_prefix_strategy postmaster backup_storage_directory postmaster backup_storage_url postmaster barrier_interval_ms postmaster checkpoint_frequency postmaster enable_tracing +postmaster license_key postmaster max_concurrent_creating_streaming_jobs postmaster pause_on_next_bootstrap user application_name @@ -19,6 +21,7 @@ user background_ddl user batch_enable_distributed_dml user batch_parallelism user bytea_output +user cdc_source_wait_streaming_start_timeout user client_encoding user client_min_messages user create_compaction_group_for_mv diff --git a/e2e_test/batch/functions/at_time_zone.slt.part b/e2e_test/batch/functions/at_time_zone.slt.part index e24bf7e41ef58..cfd89b3312965 100644 --- a/e2e_test/batch/functions/at_time_zone.slt.part +++ b/e2e_test/batch/functions/at_time_zone.slt.part @@ -21,3 +21,19 @@ query T select '2022-11-06 01:00:00'::timestamp AT TIME ZONE 'us/pacific'; ---- 2022-11-06 09:00:00+00:00 + +# non-literal zone +statement ok +create table t (local timestamp, tz varchar); + +statement ok +insert into t values ('2024-06-10 12:00:00', 'US/Pacific'), ('2024-06-10 13:00:00', 'Asia/Singapore'); + +query T +select local AT TIME ZONE tz from t order by 1; +---- +2024-06-10 05:00:00+00:00 +2024-06-10 19:00:00+00:00 + +statement ok +drop table t; diff --git a/e2e_test/batch/functions/to_char.slt.part b/e2e_test/batch/functions/to_char.slt.part index b4d10ec34d49c..4398efa3ff9cf 100644 --- a/e2e_test/batch/functions/to_char.slt.part +++ b/e2e_test/batch/functions/to_char.slt.part @@ -106,51 +106,17 @@ select to_char('-23:22:57.124562'::interval, 'HH12 MI SS MS US'); ---- -11 -22 -57 -124 -124562 -query error +query error invalid format specification for an interval value select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MM SS tzhtzm'); ----- -db error: ERROR: Failed to run the query - -Caused by these errors (recent errors listed first): - 1: Expr error - 2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates. - -query error +query error invalid format specification for an interval value select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS TZH:TZM'); ----- -db error: ERROR: Failed to run the query -Caused by these errors (recent errors listed first): - 1: Expr error - 2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates. - - -query error +query error invalid format specification for an interval value select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS TZH'); ----- -db error: ERROR: Failed to run the query -Caused by these errors (recent errors listed first): - 1: Expr error - 2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates. - - -query error +query error invalid format specification for an interval value select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS Month'); ----- -db error: ERROR: Failed to run the query - -Caused by these errors (recent errors listed first): - 1: Expr error - 2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates. - -query error +query error invalid format specification for an interval value select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS Mon'); ----- -db error: ERROR: Failed to run the query - -Caused by these errors (recent errors listed first): - 1: Expr error - 2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates. diff --git a/e2e_test/batch/over_window/main.slt.part b/e2e_test/batch/over_window/main.slt.part index dca370c0306ee..9ef0a7572380d 100644 --- a/e2e_test/batch/over_window/main.slt.part +++ b/e2e_test/batch/over_window/main.slt.part @@ -1 +1,2 @@ include ./generated/main.slt.part +include ./session/mod.slt.part diff --git a/e2e_test/batch/over_window/session/mod.slt.part b/e2e_test/batch/over_window/session/mod.slt.part new file mode 100644 index 0000000000000..eb0e09675cff7 --- /dev/null +++ b/e2e_test/batch/over_window/session/mod.slt.part @@ -0,0 +1,61 @@ +# Because currently general streaming version of session window is not supported yet, +# we only add e2e for batch mode. + +statement ok +create table t ( + tm timestamp, + foo int, + bar int +); + +statement ok +create view v1 as +select + *, + first_value(tm) over (partition by bar order by tm session with gap '10 minutes') as window_start, + last_value(tm) over (partition by bar order by tm session with gap '10 minutes') as window_end +from t; + +statement ok +insert into t values + ('2023-05-06 16:51:00', 1, 100) +, ('2023-05-06 16:56:00', 8, 100) +, ('2023-05-06 17:30:00', 3, 200) +, ('2023-05-06 17:35:00', 5, 100) +, ('2023-05-06 17:59:00', 4, 100) +, ('2023-05-06 18:01:00', 6, 200) +; + +query TiiTT +select * from v1 order by tm; +---- +2023-05-06 16:51:00 1 100 2023-05-06 16:51:00 2023-05-06 16:56:00 +2023-05-06 16:56:00 8 100 2023-05-06 16:51:00 2023-05-06 16:56:00 +2023-05-06 17:30:00 3 200 2023-05-06 17:30:00 2023-05-06 17:30:00 +2023-05-06 17:35:00 5 100 2023-05-06 17:35:00 2023-05-06 17:35:00 +2023-05-06 17:59:00 4 100 2023-05-06 17:59:00 2023-05-06 17:59:00 +2023-05-06 18:01:00 6 200 2023-05-06 18:01:00 2023-05-06 18:01:00 + +statement ok +insert into t values + ('2023-05-06 18:08:00', 7, 100) +, ('2023-05-06 18:10:00', 9, 200) +; + +query TiiTT +select * from v1 order by tm; +---- +2023-05-06 16:51:00 1 100 2023-05-06 16:51:00 2023-05-06 16:56:00 +2023-05-06 16:56:00 8 100 2023-05-06 16:51:00 2023-05-06 16:56:00 +2023-05-06 17:30:00 3 200 2023-05-06 17:30:00 2023-05-06 17:30:00 +2023-05-06 17:35:00 5 100 2023-05-06 17:35:00 2023-05-06 17:35:00 +2023-05-06 17:59:00 4 100 2023-05-06 17:59:00 2023-05-06 18:08:00 +2023-05-06 18:01:00 6 200 2023-05-06 18:01:00 2023-05-06 18:10:00 +2023-05-06 18:08:00 7 100 2023-05-06 17:59:00 2023-05-06 18:08:00 +2023-05-06 18:10:00 9 200 2023-05-06 18:01:00 2023-05-06 18:10:00 + +statement ok +drop view v1; + +statement ok +drop table t; diff --git a/e2e_test/batch/subquery/lateral_subquery.slt.part b/e2e_test/batch/subquery/lateral_subquery.slt.part index 04e93a3d397ce..98077487828e4 100644 --- a/e2e_test/batch/subquery/lateral_subquery.slt.part +++ b/e2e_test/batch/subquery/lateral_subquery.slt.part @@ -82,3 +82,32 @@ drop table all_sales; statement ok drop table salesperson; +statement ok +CREATE TABLE r(ts TIMESTAMPTZ, src_id int, dev_id int); + +statement ok +INSERT INTO r VALUES +('2024-06-20T19:00:22Z'::TIMESTAMPTZ, 2, 2), +('2024-06-20T19:00:22Z'::TIMESTAMPTZ, 1, 3), +('2024-06-20T19:00:23Z'::TIMESTAMPTZ, 1, 2), +('2024-06-20T19:00:24Z'::TIMESTAMPTZ, 2, 1), +('2024-06-20T19:00:24Z'::TIMESTAMPTZ, 1, 2), +('2024-06-20T19:00:25Z'::TIMESTAMPTZ, 2, 1); + +query TII rowsort +SELECT e.ts AS e_ts, d.* +FROM ( + SELECT '2024-06-20T19:01:00Z'::TIMESTAMPTZ ts, 1::INT AS src_id) e +JOIN LATERAL +( + SELECT DISTINCT ON(src_id, dev_id) * + FROM r + WHERE r.src_id = e.src_id AND r.ts <= e.ts + ORDER BY src_id, dev_id, ts DESC +)d on true; +---- +2024-06-20 19:01:00+00:00 2024-06-20 19:00:22+00:00 1 3 +2024-06-20 19:01:00+00:00 2024-06-20 19:00:24+00:00 1 2 + +statement ok +DROP TABLE r CASCADE; diff --git a/e2e_test/commands/README.md b/e2e_test/commands/README.md new file mode 100644 index 0000000000000..f3583a28072c7 --- /dev/null +++ b/e2e_test/commands/README.md @@ -0,0 +1,10 @@ +# "built-in" Sqllogictest system commands + +Scripts (executables) in this directory are expected to be used as `system` commands in sqllogictests. You can use any language like bash, python, zx. + +They will be loaded in `PATH` by `risedev slt`, and thus function as kind of "built-in" commands. + +Only general commands should be put here. +If the script is ad-hoc (only used for one test), it's better to put next to the test file. + +See the [connector development guide](http://risingwavelabs.github.io/risingwave/connector/intro.html#end-to-end-tests) for more information about how to test. diff --git a/e2e_test/commands/internal_table.mjs b/e2e_test/commands/internal_table.mjs new file mode 100755 index 0000000000000..b51cc0313dca2 --- /dev/null +++ b/e2e_test/commands/internal_table.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env zx + +// zx: A tool for writing better scripts +// https://google.github.io/zx/ + +const { + name: job_name, + type: table_type, + count: count, +} = minimist(process.argv.slice(3), { + string: ["name", "type"], + boolean: ["count"], +}); + +// Return an array of CSV string +async function psql(query) { + return ( + await $` +psql -h $RISEDEV_RW_FRONTEND_LISTEN_ADDRESS -p $RISEDEV_RW_FRONTEND_PORT -U root -d dev \ +--csv --tuples-only -c ${query} +` + ) + .toString() + .trim() + .split("\n") + .filter((line) => line.trim() != ""); +} + +// If `table_type` is null, return all internal tables for the job. +// If `job_name` is null, return all jobs' internal tables. +async function select_internal_table(job_name, table_type) { + // Note: if we have `t1`, and `t1_balabala`, the latter one will also be matched 😄. + const internal_tables = await psql( + `select name from rw_internal_tables where name like '__internal_${job_name}_%_${table_type}_%'` + ); + if (internal_tables.length == 0) { + throw new Error( + `No internal tables found for the pattern '__internal_${job_name}_%_${table_type}_%'` + ); + } + + const res = new Map( + await Promise.all( + internal_tables.map(async (t) => { + let rows = await psql(`select * from ${t}`); + return [t, rows]; + }) + ) + ); + return res; +} + +const tables = await select_internal_table(job_name, table_type); +for (const [table_name, rows] of tables) { + if (tables.size > 1) { + console.log(`Table: ${table_name}`); + } + if (count) { + console.log(`count: ${rows.length}`); + } else { + if (rows.length == 0) { + console.log("(empty)"); + } else { + console.log(`${rows.join("\n")}`); + } + } +} diff --git a/e2e_test/commands/mysql b/e2e_test/commands/mysql new file mode 100755 index 0000000000000..881ecd93811ac --- /dev/null +++ b/e2e_test/commands/mysql @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# A simple wrapper around `mysql` but add an `-u` option to specify the user. +# The environment variable `RISEDEV_MYSQL_USER` will be set by `risedev-env`. + +# Strip the current directory from the PATH to access the system `mysql`. +REAL_PATH=$(echo "$PATH" | sed "s,$(dirname "$0"):,,g") + +PATH=${REAL_PATH} mysql -u "${RISEDEV_MYSQL_USER}" "$@" diff --git a/e2e_test/ddl/alter_parallelism.slt b/e2e_test/ddl/alter_parallelism.slt index 4d774321f1d64..025496ca1c571 100644 --- a/e2e_test/ddl/alter_parallelism.slt +++ b/e2e_test/ddl/alter_parallelism.slt @@ -13,9 +13,6 @@ create view mview_parallelism as select m.name, tf.parallelism from rw_materiali statement ok create view sink_parallelism as select s.name, tf.parallelism from rw_sinks s, rw_table_fragments tf where s.id = tf.table_id; -statement ok -create view subscription_parallelism as select s.name, tf.parallelism from rw_subscriptions s, rw_table_fragments tf where s.id = tf.table_id; - statement ok create view fragment_parallelism as select t.name as table_name, f.fragment_id, f.parallelism from rw_fragments f, rw_tables t where f.table_id = t.id; @@ -97,28 +94,9 @@ select parallelism from sink_parallelism where name = 's'; ---- FIXED(4) -statement ok -create subscription subscription1 from t with (retention = '1D'); - -query T -select parallelism from subscription_parallelism where name = 'subscription1'; ----- -ADAPTIVE - -statement ok -alter subscription subscription1 set parallelism = 4; - -query T -select parallelism from subscription_parallelism where name = 'subscription1'; ----- -FIXED(4) - statement ok drop sink s; -statement ok -drop subscription subscription1; - statement ok drop materialized view m_join; @@ -179,8 +157,5 @@ drop view mview_parallelism; statement ok drop view sink_parallelism; -statement ok -drop view subscription_parallelism; - statement ok drop view fragment_parallelism; \ No newline at end of file diff --git a/e2e_test/ddl/alter_set_schema.slt b/e2e_test/ddl/alter_set_schema.slt index 9ea95e9cc09c2..74dcc5a77e64a 100644 --- a/e2e_test/ddl/alter_set_schema.slt +++ b/e2e_test/ddl/alter_set_schema.slt @@ -53,13 +53,14 @@ CREATE SOURCE test_source (v INT) WITH ( statement ok ALTER SOURCE test_source SET SCHEMA test_schema; -query TT +query TT rowsort SELECT name AS sourcename, nspname AS schemaname FROM rw_sources JOIN pg_namespace ON pg_namespace.oid = rw_sources.schema_id WHERE nspname = 'test_schema'; ---- test_source test_schema +test_table test_schema statement ok CREATE SINK test_sink AS SELECT u FROM test_schema.test_table WITH ( diff --git a/e2e_test/ddl/schema.slt b/e2e_test/ddl/schema.slt index 218b4bf08bb08..d998233b3b088 100644 --- a/e2e_test/ddl/schema.slt +++ b/e2e_test/ddl/schema.slt @@ -67,3 +67,35 @@ drop schema Myschema; statement ok drop schema "Myschema"; + +# test derived schema name and authorization with user +statement ok +create user user_for_authorization; + +statement ok +create schema if not exists authorization user_for_authorization; + +statement ok +create schema myschema authorization user_for_authorization; + +query T rowsort +show schemas; +---- +information_schema +myschema +pg_catalog +public +rw_catalog +user_for_authorization + +statement error Permission denied +drop user user_for_authorization; + +statement ok +drop schema myschema; + +statement ok +drop schema user_for_authorization; + +statement ok +drop user user_for_authorization; diff --git a/e2e_test/ddl/secret.slt b/e2e_test/ddl/secret.slt new file mode 100644 index 0000000000000..7c11e2e6245a3 --- /dev/null +++ b/e2e_test/ddl/secret.slt @@ -0,0 +1,23 @@ +statement error secret backend "fake-backend" is not supported +create secret secret_1 with ( + backend = 'fake-backend' +) as 'demo_secret'; + +statement ok +create secret secret_1 with ( + backend = 'meta' +) as 'demo_secret'; + +# wait for support for hashicorp_vault backend +# statement ok +# create secret secret_2 with ( +# backend = 'hashicorp_vault' +# ); + +query T +show secrets; +---- +secret_1 + +statement ok +drop secret secret_1; diff --git a/e2e_test/ddl/show.slt b/e2e_test/ddl/show.slt index f2bd63b4e3768..abfd5adb45fb8 100644 --- a/e2e_test/ddl/show.slt +++ b/e2e_test/ddl/show.slt @@ -102,12 +102,12 @@ show tables from public; t3 query T -show tables from public like "t_"; +show tables from public like 't_'; ---- t3 query T -show tables from public like "_t"; +show tables from public like '_t'; ---- query T @@ -215,3 +215,37 @@ tablespace character varying false NULL hasindexes boolean false NULL ispopulated boolean false NULL definition character varying false NULL + +# show tables according to search path + +statement ok +create schema show_another_schema; + +statement ok +create table show_another_schema.t_another (v1 int); + +statement ok +create table t4 (v1 int); + +query T +show tables; +---- +t4 + +statement ok +set search_path to show_another_schema, public; + +query T +show tables; +---- +t_another +t4 + +statement ok +drop table t4; + +statement ok +drop table show_another_schema.t_another; + +statement ok +drop schema show_another_schema; diff --git a/e2e_test/error_ui/extended/main.slt b/e2e_test/error_ui/extended/main.slt index 67db6ccf3393f..6b9be9d26b14f 100644 --- a/e2e_test/error_ui/extended/main.slt +++ b/e2e_test/error_ui/extended/main.slt @@ -4,8 +4,9 @@ selet 1; db error: ERROR: Failed to prepare the statement Caused by: - sql parser error: Expected an SQL statement, found: selet at line:1, column:6 -Near "selet" + sql parser error: expected statement, found: selet +LINE 1: selet 1; + ^ query error @@ -15,4 +16,5 @@ db error: ERROR: Failed to execute the statement Caused by these errors (recent errors listed first): 1: Expr error - 2: Division by zero + 2: error while evaluating expression `general_div('1', '0')` + 3: Division by zero diff --git a/e2e_test/error_ui/simple/expr.slt b/e2e_test/error_ui/simple/expr.slt new file mode 100644 index 0000000000000..b983baae99a01 --- /dev/null +++ b/e2e_test/error_ui/simple/expr.slt @@ -0,0 +1,71 @@ +# Regular function +query error +select pow(114, 514); +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `pow_f64('114', '514')` + 3: Numeric out of range: overflow + + +# Nullable arguments +query error +select array_position(array[1, null, 2, null], null, null::int); +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `array_position_start('{1,NULL,2,NULL}', NULL, NULL)` + 3: Invalid parameter start: initial position must not be null + + +# Operator +query error +select 11111111444 * 51444444444444444; +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `general_mul('11111111444', '51444444444444444')` + 3: Numeric out of range + + +# Cast +query error +select 'foo'::bigint; +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `str_parse('foo')` + 3: Parse error: bigint invalid digit found in string + + +# Prebuild context +# TODO: not included in the error message +query error +select jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '"foo"'); +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `jsonb_path_exists3('{"a": [1, 2, 3, 4, 5]}', '"foo"')` + 3: Invalid parameter jsonpath: "vars" argument is not an object + + +# Variadic arguments +query error +select format('%L %s', 'Hello', 'World'); +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `format('Hello', 'World')` + 3: Unsupported function: unsupported specifier type 'L' diff --git a/e2e_test/error_ui/simple/license.slt b/e2e_test/error_ui/simple/license.slt new file mode 100644 index 0000000000000..22bd4c60cf689 --- /dev/null +++ b/e2e_test/error_ui/simple/license.slt @@ -0,0 +1,59 @@ +# Set the license key to a free tier key. +statement ok +ALTER SYSTEM SET license_key TO 'eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydy10ZXN0IiwidGllciI6ImZyZWUiLCJpc3MiOiJ0ZXN0LnJpc2luZ3dhdmUuY29tIiwiZXhwIjo5OTk5OTk5OTk5fQ.ALC3Kc9LI6u0S-jeMB1YTxg1k8Azxwvc750ihuSZgjA_e1OJC9moxMvpLrHdLZDzCXHjBYi0XJ_1lowmuO_0iPEuPqN5AFpDV1ywmzJvGmMCMtw3A2wuN7hhem9OsWbwe6lzdwrefZLipyo4GZtIkg5ZdwGuHzm33zsM-X5gl_Ns4P6axHKiorNSR6nTAyA6B32YVET_FAM2YJQrXqpwA61wn1XLfarZqpdIQyJ5cgyiC33BFBlUL3lcRXLMLeYe6TjYGeV4K63qARCjM9yeOlsRbbW5ViWeGtR2Yf18pN8ysPXdbaXm_P_IVhl3jCTDJt9ctPh6pUCbkt36FZqO9A'; + +query error +SELECT rw_test_paid_tier(); +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `test_paid_tier()` + 3: feature TestPaid is only available for tier Paid and above, while the current tier is Free + +Hint: You may want to set a license key with `ALTER SYSTEM SET license_key = '...';` command. + + +# Set the license key to an invalid key. +statement ok +ALTER SYSTEM SET license_key TO 'invalid'; + +query error +SELECT rw_test_paid_tier(); +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `test_paid_tier()` + 3: feature TestPaid is not available due to license error + 4: invalid license key + 5: InvalidToken + + +# Set the license key to empty. This demonstrates the default behavior in production, i.e., free tier. +statement ok +ALTER SYSTEM SET license_key TO ''; + +query error +SELECT rw_test_paid_tier(); +---- +db error: ERROR: Failed to run the query + +Caused by these errors (recent errors listed first): + 1: Expr error + 2: error while evaluating expression `test_paid_tier()` + 3: feature TestPaid is only available for tier Paid and above, while the current tier is Free + +Hint: You may want to set a license key with `ALTER SYSTEM SET license_key = '...';` command. + + +# Set the license key to default. In debug mode, this will set the license key to a paid tier key. +statement ok +ALTER SYSTEM SET license_key TO DEFAULT; + +query T +SELECT rw_test_paid_tier(); +---- +t diff --git a/e2e_test/error_ui/simple/main.slt b/e2e_test/error_ui/simple/main.slt index 8ef82e1f0d1c7..f09e47302f3c6 100644 --- a/e2e_test/error_ui/simple/main.slt +++ b/e2e_test/error_ui/simple/main.slt @@ -4,8 +4,9 @@ selet 1; db error: ERROR: Failed to run the query Caused by: - sql parser error: Expected an SQL statement, found: selet at line:1, column:6 -Near "selet" + sql parser error: expected statement, found: selet +LINE 1: selet 1; + ^ statement error @@ -13,8 +14,9 @@ create function int_42() returns int as int_42 using link '555.0.0.1:8815'; ---- db error: ERROR: Failed to run the query -Caused by: - Flight service error: invalid address: 555.0.0.1:8815, err: failed to parse address: http://555.0.0.1:8815: invalid IPv4 address +Caused by these errors (recent errors listed first): + 1: failed to parse address: http://555.0.0.1:8815 + 2: invalid IPv4 address statement error @@ -59,7 +61,8 @@ db error: ERROR: Failed to run the query Caused by these errors (recent errors listed first): 1: Expr error - 2: Division by zero + 2: error while evaluating expression `general_div('1', '0')` + 3: Division by zero query error @@ -69,7 +72,8 @@ db error: ERROR: Failed to run the query Caused by these errors (recent errors listed first): 1: Expr error - 2: Division by zero + 2: error while evaluating expression `general_div('1', '0')` + 3: Division by zero statement error diff --git a/e2e_test/error_ui/simple/recovery.slt b/e2e_test/error_ui/simple/recovery.slt index b47afc94c47ad..e3830be6d25c8 100644 --- a/e2e_test/error_ui/simple/recovery.slt +++ b/e2e_test/error_ui/simple/recovery.slt @@ -1,15 +1,15 @@ -# TODO: the test triggers a recovery caused by a known issue: https://github.com/risingwavelabs/risingwave/issues/12474. +# TODO: the test triggers a recovery caused by a known issue: https://github.com/risingwavelabs/risingwave/issues/11915. # We should consider using a mechanism designed for testing recovery instead of depending on a bug. statement ok -create table t (v int); +create table t (v decimal); statement ok -create materialized view mv as select generate_series(1, 10), coalesce(pg_sleep(2), v) / 0 bomb from t; +create materialized view mv as select sum(coalesce(pg_sleep(1), v)) from t; -# The bomb will be triggered after 2 seconds of sleep, so the insertion should return successfully. +# The bomb will be triggered after 1 seconds of sleep, so the insertion should return successfully. statement ok -insert into t values (1); +insert into t values (4e28), (4e28); # Wait for recovery to complete. sleep 15s @@ -25,7 +25,7 @@ with error as ( limit 1 ) select -case when error like '%Actor % exited unexpectedly: Executor error: Chunk operation error: Division by zero%' then 'ok' +case when error like '%Actor % exited unexpectedly: Executor error: %Numeric out of range%' then 'ok' else error end as result from error; diff --git a/e2e_test/iceberg/main.py b/e2e_test/iceberg/main.py index 03066033c6d24..01017f3db783d 100644 --- a/e2e_test/iceberg/main.py +++ b/e2e_test/iceberg/main.py @@ -50,7 +50,7 @@ def execute_slt(args, slt): cmd = f"sqllogictest -p {rw_config['port']} -d {rw_config['db']} {slt}" print(f"Command line is [{cmd}]") subprocess.run(cmd, shell=True, check=True) - time.sleep(30) + time.sleep(15) def verify_result(args, verify_sql, verify_schema, verify_data): @@ -129,6 +129,7 @@ def drop_table(args, drop_sqls): verify_sql = test_case.get("verify_sql") print(f"verify_sql:{verify_sql}") verify_data = test_case.get("verify_data") + verify_slt = test_case.get("verify_slt") cmp_sqls = test_case.get("cmp_sqls") drop_sqls = test_case["drop_sqls"] config = configparser.ConfigParser() @@ -146,6 +147,8 @@ def drop_table(args, drop_sqls): verify_result(config, verify_sql, verify_schema, verify_data) if cmp_sqls is not None and cmp_sqls != "" and len(cmp_sqls) == 2: compare_sql(config, cmp_sqls) + if verify_slt is not None and verify_slt != "": + execute_slt(config, verify_slt) if drop_sqls is not None and drop_sqls != "": drop_table(config, drop_sqls) diff --git a/e2e_test/iceberg/test_case/append_only_with_checkpoint_interval.slt b/e2e_test/iceberg/test_case/append_only_with_checkpoint_interval.slt index 7d1f9254e4683..0a8a042a86ac4 100644 --- a/e2e_test/iceberg/test_case/append_only_with_checkpoint_interval.slt +++ b/e2e_test/iceberg/test_case/append_only_with_checkpoint_interval.slt @@ -29,7 +29,7 @@ CREATE SINK sink1 AS select * from mv1 WITH ( table.name = 't1', catalog.name = 'demo', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', @@ -45,7 +45,7 @@ CREATE SINK sink2 AS select * from mv1 WITH ( table.name = 't2', catalog.name = 'demo', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/cdc/load.slt b/e2e_test/iceberg/test_case/cdc/load.slt index e9f1d815d20cb..df0c319990374 100644 --- a/e2e_test/iceberg/test_case/cdc/load.slt +++ b/e2e_test/iceberg/test_case/cdc/load.slt @@ -28,7 +28,7 @@ CREATE SINK s1 AS select * from products WITH ( database.name = 'demo_db', table.name = 'demo_table', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/iceberg_sink_no_partition_append_only_table.slt b/e2e_test/iceberg/test_case/iceberg_sink_no_partition_append_only_table.slt index 505bf0ba7d21c..658a9ae8563e1 100644 --- a/e2e_test/iceberg/test_case/iceberg_sink_no_partition_append_only_table.slt +++ b/e2e_test/iceberg/test_case/iceberg_sink_no_partition_append_only_table.slt @@ -28,7 +28,7 @@ CREATE SINK s6 AS select * from mv6 WITH ( table.name = 'no_partition_append_only_table', catalog.name = 'demo', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/iceberg_sink_no_partition_append_only_table_verify.slt b/e2e_test/iceberg/test_case/iceberg_sink_no_partition_append_only_table_verify.slt new file mode 100644 index 0000000000000..a789782367261 --- /dev/null +++ b/e2e_test/iceberg/test_case/iceberg_sink_no_partition_append_only_table_verify.slt @@ -0,0 +1,25 @@ +statement ok +CREATE SOURCE iceberg_source +WITH ( + connector = 'iceberg', + s3.endpoint = 'http://127.0.0.1:9301', + s3.region = 'us-east-1', + s3.access.key = 'hummockadmin', + s3.secret.key = 'hummockadmin', + catalog.type = 'storage', + warehouse.path = 's3a://icebergdata/demo', + database.name = 'demo_db', + table.name = 'no_partition_append_only_table', +); + +query I +SELECT id from iceberg_source ORDER by id; +---- +1 +2 +3 +4 +5 + +statement ok +DROP SOURCE iceberg_source diff --git a/e2e_test/iceberg/test_case/iceberg_sink_no_partition_upsert_table.slt b/e2e_test/iceberg/test_case/iceberg_sink_no_partition_upsert_table.slt index b59c7013290ec..1ff15295c745a 100644 --- a/e2e_test/iceberg/test_case/iceberg_sink_no_partition_upsert_table.slt +++ b/e2e_test/iceberg/test_case/iceberg_sink_no_partition_upsert_table.slt @@ -16,7 +16,7 @@ CREATE SINK s6 AS select mv6.id as id, mv6.v1 as v1, mv6.v2 as v2, mv6.v3 as v3, database.name = 'demo_db', table.name = 'no_partition_upsert_table', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/iceberg_sink_partition_append_only_table.slt b/e2e_test/iceberg/test_case/iceberg_sink_partition_append_only_table.slt index da55b35626ff3..bd328f7834d6f 100644 --- a/e2e_test/iceberg/test_case/iceberg_sink_partition_append_only_table.slt +++ b/e2e_test/iceberg/test_case/iceberg_sink_partition_append_only_table.slt @@ -28,7 +28,7 @@ CREATE SINK s6 AS select * from mv6 WITH ( table.name = 'partition_append_only_table', catalog.name = 'demo', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/iceberg_sink_partition_append_only_table_verify.slt b/e2e_test/iceberg/test_case/iceberg_sink_partition_append_only_table_verify.slt new file mode 100644 index 0000000000000..58d848b54cb21 --- /dev/null +++ b/e2e_test/iceberg/test_case/iceberg_sink_partition_append_only_table_verify.slt @@ -0,0 +1,25 @@ +statement ok +CREATE SOURCE iceberg_source +WITH ( + connector = 'iceberg', + s3.endpoint = 'http://127.0.0.1:9301', + s3.region = 'us-east-1', + s3.access.key = 'hummockadmin', + s3.secret.key = 'hummockadmin', + catalog.type = 'storage', + warehouse.path = 's3a://icebergdata/demo', + database.name = 'demo_db', + table.name = 'partition_append_only_table', +); + +query I +SELECT id from iceberg_source ORDER by id; +---- +1 +2 +3 +4 +5 + +statement ok +DROP SOURCE iceberg_source diff --git a/e2e_test/iceberg/test_case/iceberg_sink_partition_upsert_table.slt b/e2e_test/iceberg/test_case/iceberg_sink_partition_upsert_table.slt index 20800c1b4787d..1435e03877f40 100644 --- a/e2e_test/iceberg/test_case/iceberg_sink_partition_upsert_table.slt +++ b/e2e_test/iceberg/test_case/iceberg_sink_partition_upsert_table.slt @@ -16,7 +16,7 @@ CREATE SINK s6 AS select mv6.id as id, mv6.v1 as v1, mv6.v2 as v2, mv6.v3 as v3, database.name = 'demo_db', table.name = 'partition_upsert_table', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/iceberg_sink_range_partition_append_only_table.slt b/e2e_test/iceberg/test_case/iceberg_sink_range_partition_append_only_table.slt index 2a8d562b66068..e71fe49f31f9c 100644 --- a/e2e_test/iceberg/test_case/iceberg_sink_range_partition_append_only_table.slt +++ b/e2e_test/iceberg/test_case/iceberg_sink_range_partition_append_only_table.slt @@ -28,7 +28,7 @@ CREATE SINK s6 AS select * from mv6 WITH ( table.name = 'range_partition_append_only_table', catalog.name = 'demo', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/iceberg_sink_range_partition_append_only_table_verify.slt b/e2e_test/iceberg/test_case/iceberg_sink_range_partition_append_only_table_verify.slt new file mode 100644 index 0000000000000..eaa6b3d95eb6a --- /dev/null +++ b/e2e_test/iceberg/test_case/iceberg_sink_range_partition_append_only_table_verify.slt @@ -0,0 +1,25 @@ +statement ok +CREATE SOURCE iceberg_source +WITH ( + connector = 'iceberg', + s3.endpoint = 'http://127.0.0.1:9301', + s3.region = 'us-east-1', + s3.access.key = 'hummockadmin', + s3.secret.key = 'hummockadmin', + catalog.type = 'storage', + warehouse.path = 's3a://icebergdata/demo', + database.name = 'demo_db', + table.name = 'range_partition_append_only_table', +); + +query I +SELECT id from iceberg_source ORDER by id; +---- +1 +2 +3 +4 +5 + +statement ok +DROP SOURCE iceberg_source diff --git a/e2e_test/iceberg/test_case/iceberg_sink_range_partition_upsert_table.slt b/e2e_test/iceberg/test_case/iceberg_sink_range_partition_upsert_table.slt index 6e963b0e16615..daf1ceaa40bf8 100644 --- a/e2e_test/iceberg/test_case/iceberg_sink_range_partition_upsert_table.slt +++ b/e2e_test/iceberg/test_case/iceberg_sink_range_partition_upsert_table.slt @@ -16,7 +16,7 @@ CREATE SINK s6 AS select mv6.id as id, mv6.v1 as v1, mv6.v2 as v2, mv6.v3 as v3, database.name = 'demo_db', table.name = 'range_partition_upsert_table', catalog.type = 'storage', - warehouse.path = 's3://icebergdata/demo', + warehouse.path = 's3a://icebergdata/demo', s3.endpoint = 'http://127.0.0.1:9301', s3.region = 'us-east-1', s3.access.key = 'hummockadmin', diff --git a/e2e_test/iceberg/test_case/no_partition_append_only.toml b/e2e_test/iceberg/test_case/no_partition_append_only.toml index 58f5900586d02..7d2952c508756 100644 --- a/e2e_test/iceberg/test_case/no_partition_append_only.toml +++ b/e2e_test/iceberg/test_case/no_partition_append_only.toml @@ -33,6 +33,8 @@ verify_data = """ 5,5,5000,5.5,5.55,5-5,true,2022-03-15,2022-03-15 05:00:00+00:00,2022-03-15 05:00:00,none """ +verify_slt = 'test_case/iceberg_sink_no_partition_append_only_table_verify.slt' + drop_sqls = [ 'DROP TABLE IF EXISTS demo_db.no_partition_append_only_table', 'DROP SCHEMA IF EXISTS demo_db' diff --git a/e2e_test/iceberg/test_case/partition_append_only.toml b/e2e_test/iceberg/test_case/partition_append_only.toml index 9fd55ee0b0044..b36dfb98e369f 100644 --- a/e2e_test/iceberg/test_case/partition_append_only.toml +++ b/e2e_test/iceberg/test_case/partition_append_only.toml @@ -35,6 +35,8 @@ verify_data = """ 5,5,5000,5.5,5.55,5-5,true,2022-03-15,2022-03-15 05:00:00+00:00,2022-03-15 05:00:00,none """ +verify_slt = 'test_case/iceberg_sink_partition_append_only_table_verify.slt' + drop_sqls = [ 'DROP TABLE IF EXISTS demo_db.partition_append_only_table', 'DROP SCHEMA IF EXISTS demo_db' diff --git a/e2e_test/iceberg/test_case/range_partition_append_only.toml b/e2e_test/iceberg/test_case/range_partition_append_only.toml index d8c12d744584a..d54407f91dc10 100644 --- a/e2e_test/iceberg/test_case/range_partition_append_only.toml +++ b/e2e_test/iceberg/test_case/range_partition_append_only.toml @@ -35,6 +35,8 @@ verify_data = """ 5,5,5000,5.5,5.55,5-5,true,2022-03-15,2022-03-15 05:00:00+00:00,2022-03-15 05:00:00,none """ +verify_slt = 'test_case/iceberg_sink_range_partition_append_only_table_verify.slt' + drop_sqls = [ 'DROP TABLE IF EXISTS demo_db.range_partition_append_only_table', 'DROP SCHEMA IF EXISTS demo_db' diff --git a/e2e_test/s3/fs_source_v2.py b/e2e_test/s3/fs_source_v2.py index a687c9be19c9d..760b8d07a09a5 100644 --- a/e2e_test/s3/fs_source_v2.py +++ b/e2e_test/s3/fs_source_v2.py @@ -29,6 +29,7 @@ def format_json(data): for file in data ] + def format_csv(data, with_header): csv_files = [] diff --git a/e2e_test/schema_registry/alter_sr.slt b/e2e_test/schema_registry/alter_sr.slt deleted file mode 100644 index 8daf41d87b633..0000000000000 --- a/e2e_test/schema_registry/alter_sr.slt +++ /dev/null @@ -1,80 +0,0 @@ -# Before running this test, seed data into kafka: -# python3 e2e_test/schema_registry/pb.py - -statement ok -CREATE SOURCE src_user WITH ( - connector = 'kafka', - topic = 'sr_pb_test', - properties.bootstrap.server = 'message_queue:29092', - scan.startup.mode = 'earliest' -) -FORMAT PLAIN ENCODE PROTOBUF( - schema.registry = 'http://message_queue:8081', - message = 'test.User' -); - -statement ok -CREATE MATERIALIZED VIEW mv_user AS SELECT * FROM src_user; - -statement ok -CREATE TABLE t_user WITH ( - connector = 'kafka', - topic = 'sr_pb_test', - properties.bootstrap.server = 'message_queue:29092', - scan.startup.mode = 'earliest' -) -FORMAT PLAIN ENCODE PROTOBUF( - schema.registry = 'http://message_queue:8081', - message = 'test.User' -); - -statement error -SELECT age FROM mv_user; - -statement error -SELECT age FROM t_user; - -# Push more events with extended fields -system ok -python3 e2e_test/schema_registry/pb.py "message_queue:29092" "http://message_queue:8081" "sr_pb_test" 5 user_with_more_fields - -sleep 5s - -# Refresh source schema -statement ok -ALTER SOURCE src_user REFRESH SCHEMA; - -statement ok -CREATE MATERIALIZED VIEW mv_user_more AS SELECT * FROM src_user; - -# Refresh table schema -statement ok -ALTER TABLE t_user REFRESH SCHEMA; - -query IIII -SELECT COUNT(*), MAX(age), MIN(age), SUM(age) FROM mv_user_more; ----- -25 4 0 10 - -# Push more events with extended fields -system ok -python3 e2e_test/schema_registry/pb.py "message_queue:29092" "http://message_queue:8081" "sr_pb_test" 5 user_with_more_fields - -sleep 5s - -query IIII -SELECT COUNT(*), MAX(age), MIN(age), SUM(age) FROM t_user; ----- -30 4 0 10 - -statement ok -DROP MATERIALIZED VIEW mv_user_more; - -statement ok -DROP TABLE t_user; - -statement ok -DROP MATERIALIZED VIEW mv_user; - -statement ok -DROP SOURCE src_user; diff --git a/e2e_test/schema_registry/pb.slt b/e2e_test/schema_registry/pb.slt deleted file mode 100644 index d9c0edca1b21c..0000000000000 --- a/e2e_test/schema_registry/pb.slt +++ /dev/null @@ -1,50 +0,0 @@ -# Before running this test, seed data into kafka: -# python3 e2e_test/schema_registry/pb.py - -# Create a table. -statement ok -create table sr_pb_test with ( - connector = 'kafka', - topic = 'sr_pb_test', - properties.bootstrap.server = 'message_queue:29092', - scan.startup.mode = 'earliest') -FORMAT plain ENCODE protobuf( - schema.registry = 'http://message_queue:8081', - message = 'test.User' - ); - -# for multiple schema registry nodes -statement ok -create table sr_pb_test_bk with ( - connector = 'kafka', - topic = 'sr_pb_test', - properties.bootstrap.server = 'message_queue:29092', - scan.startup.mode = 'earliest') -FORMAT plain ENCODE protobuf( - schema.registry = 'http://message_queue:8081,http://message_queue:8081', - message = 'test.User' - ); - -# Wait for source -sleep 10s - -# Flush into storage -statement ok -flush; - -query I -select count(*) from sr_pb_test; ----- -20 - -query IIT -select min(id), max(id), max((sc).file_name) from sr_pb_test; ----- -0 19 source/context_019.proto - - -statement ok -drop table sr_pb_test; - -statement ok -drop table sr_pb_test_bk; diff --git a/e2e_test/sink/clickhouse_sink.slt b/e2e_test/sink/clickhouse_sink.slt index 2adc70dcf409e..e5bac0d8d521d 100644 --- a/e2e_test/sink/clickhouse_sink.slt +++ b/e2e_test/sink/clickhouse_sink.slt @@ -1,3 +1,6 @@ +statement ok +set sink_decouple = false; + statement ok CREATE TABLE t6 (v1 int primary key, v2 bigint, v3 varchar, v4 smallint, v5 decimal); diff --git a/e2e_test/sink/iceberg_sink.slt b/e2e_test/sink/iceberg_sink.slt index 05cf2d1108b36..a7f7567075e98 100644 --- a/e2e_test/sink/iceberg_sink.slt +++ b/e2e_test/sink/iceberg_sink.slt @@ -9,7 +9,7 @@ CREATE SINK s6 AS select mv6.v1 as v1, mv6.v2 as v2, mv6.v3 as v3 from mv6 WITH connector = 'iceberg', type = 'upsert', primary_key = 'v1', - warehouse.path = 's3://iceberg', + warehouse.path = 's3a://iceberg', s3.endpoint = 'http://127.0.0.1:9301', s3.access.key = 'hummockadmin', s3.secret.key = 'hummockadmin', @@ -23,7 +23,7 @@ CREATE SINK s6 AS select mv6.v1 as v1, mv6.v2 as v2, mv6.v3 as v3 from mv6 WITH statement ok CREATE SOURCE iceberg_demo_source WITH ( connector = 'iceberg', - warehouse.path = 's3://iceberg', + warehouse.path = 's3a://iceberg', s3.endpoint = 'http://127.0.0.1:9301', s3.access.key = 'hummockadmin', s3.secret.key = 'hummockadmin', diff --git a/e2e_test/sink/kafka/avro.slt b/e2e_test/sink/kafka/avro.slt index d9fa53bc589ac..e0e339eead402 100644 --- a/e2e_test/sink/kafka/avro.slt +++ b/e2e_test/sink/kafka/avro.slt @@ -1,3 +1,6 @@ +statement ok +set sink_decouple = false; + statement ok create table from_kafka ( *, gen_i32_field int as int32_field + 2, primary key (some_key) ) include key as some_key @@ -6,7 +9,7 @@ with ( topic = 'test-rw-sink-upsert-avro', properties.bootstrap.server = 'message_queue:29092') format upsert encode avro ( - schema.registry = 'http://message_queue:8081'); + schema.registry = 'http://schemaregistry:8082'); statement ok create table into_kafka ( @@ -40,10 +43,128 @@ create sink sink0 from into_kafka with ( properties.bootstrap.server = 'message_queue:29092', primary_key = 'int32_field,string_field') format upsert encode avro ( - schema.registry = 'http://message_queue:8081'); + schema.registry = 'http://schemaregistry:8082'); + +system ok +rpk topic create test-rw-sink-plain-avro + +system ok +jq '{"schema": tojson}' << EOF | curl -X POST -H 'content-type: application/json' -d @- 'http://schemaregistry:8082/subjects/test-rw-sink-plain-avro-value/versions' +{ + "type": "record", + "name": "Simple", + "fields": [ + { + "name": "int32_field", + "type": ["null", "int"] + }, + { + "name": "string_field", + "type": ["null", "string"] + } + ] +} +EOF + +statement ok +create table from_kafka_plain +include key as raw_key +with ( + connector = 'kafka', + topic = 'test-rw-sink-plain-avro', + properties.bootstrap.server = 'message_queue:29092') +format plain encode avro ( + schema.registry = 'http://schemaregistry:8082'); + +statement ok +create sink sink_plain_key_none as select int32_field, string_field from into_kafka with ( + connector = 'kafka', + topic = 'test-rw-sink-plain-avro', + properties.bootstrap.server = 'message_queue:29092') +format plain encode avro ( + force_append_only = true, + schema.registry = 'http://schemaregistry:8082'); + +statement ok +create sink sink_plain_key_text as select int32_field, string_field from into_kafka with ( + connector = 'kafka', + topic = 'test-rw-sink-plain-avro', + properties.bootstrap.server = 'message_queue:29092', + primary_key = 'int32_field') +format plain encode avro ( + force_append_only = true, + schema.registry = 'http://schemaregistry:8082') +key encode text; + +system ok +jq '{"schema": tojson}' << EOF | curl -X POST -H 'content-type: application/json' -d @- 'http://schemaregistry:8082/subjects/test-rw-sink-plain-avro-key/versions' +{ + "type": "record", + "name": "Key", + "fields": [ + { + "name": "int32_field", + "type": ["null", "int"] + } + ] +} +EOF + +statement ok +create sink sink_plain_key_avro as select int32_field, string_field from into_kafka with ( + connector = 'kafka', + topic = 'test-rw-sink-plain-avro', + properties.bootstrap.server = 'message_queue:29092', + primary_key = 'int32_field') +format plain encode avro ( + force_append_only = true, + schema.registry = 'http://schemaregistry:8082'); sleep 2s +query ITT +select + int32_field, + string_field, + case when octet_length(raw_key) > 5 and substring(raw_key from 1 for 1) = '\x00' + then substring(raw_key from 6) -- skip indeterministic confluent schema registry header + else raw_key end +from from_kafka_plain order by string_field, raw_key; +---- +22 Rising \x022c +22 Rising \x3232 +22 Rising NULL +11 Wave \x0216 +11 Wave \x3131 +11 Wave NULL + +statement ok +drop sink sink_plain_key_avro; + +statement ok +drop sink sink_plain_key_text; + +statement ok +drop sink sink_plain_key_none; + +statement ok +drop table from_kafka_plain; + +system ok +curl -X DELETE 'http://schemaregistry:8082/subjects/test-rw-sink-plain-avro-key' + +system ok +curl -X DELETE 'http://schemaregistry:8082/subjects/test-rw-sink-plain-avro-key?permanent=true' + +system ok +curl -X DELETE 'http://schemaregistry:8082/subjects/test-rw-sink-plain-avro-value' + +system ok +curl -X DELETE 'http://schemaregistry:8082/subjects/test-rw-sink-plain-avro-value?permanent=true' + +system ok +rpk topic delete test-rw-sink-plain-avro + query TTTRRIITTTTTTTT select bool_field, @@ -72,7 +193,7 @@ create sink sink_err from into_kafka with ( properties.bootstrap.server = 'message_queue:29092', primary_key = 'int32_field,string_field') format upsert encode avro ( - schema.registry = 'http://message_queue:8081'); + schema.registry = 'http://schemaregistry:8082'); statement error field not in avro create sink sink_err as select 1 as extra_column, * from into_kafka with ( @@ -81,7 +202,7 @@ create sink sink_err as select 1 as extra_column, * from into_kafka with ( properties.bootstrap.server = 'message_queue:29092', primary_key = 'int32_field,string_field') format upsert encode avro ( - schema.registry = 'http://message_queue:8081'); + schema.registry = 'http://schemaregistry:8082'); statement error unrecognized create sink sink_err from into_kafka with ( @@ -90,7 +211,7 @@ create sink sink_err from into_kafka with ( properties.bootstrap.server = 'message_queue:29092', primary_key = 'int32_field,string_field') format upsert encode avro ( - schema.registry = 'http://message_queue:8081', + schema.registry = 'http://schemaregistry:8082', schema.registry.name.strategy = 'typo'); statement error empty field key.message @@ -100,7 +221,7 @@ create sink sink_err from into_kafka with ( properties.bootstrap.server = 'message_queue:29092', primary_key = 'int32_field,string_field') format upsert encode avro ( - schema.registry = 'http://message_queue:8081', + schema.registry = 'http://schemaregistry:8082', schema.registry.name.strategy = 'record_name_strategy'); statement ok diff --git a/e2e_test/sink/kafka/create_sink.slt b/e2e_test/sink/kafka/create_sink.slt index 1cdb4771ffd47..be4c39c7cb1c6 100644 --- a/e2e_test/sink/kafka/create_sink.slt +++ b/e2e_test/sink/kafka/create_sink.slt @@ -1,6 +1,9 @@ statement ok set rw_implicit_flush=true; +statement ok +set sink_decouple = false; + statement ok create table t_kafka ( id integer primary key, @@ -183,6 +186,61 @@ create sink invalid_pk_column from t_kafka with ( primary_key = 'id,invalid' ); +### Test sink with key encode ### + +statement error sink key encode unsupported: JSON, only TEXT supported +create sink sink_text_error from t_kafka with ( + connector = 'kafka', + properties.bootstrap.server = 'message_queue:29092', + topic = 'test-rw-sink-text-key-id', + primary_key = 'id') +format plain encode json ( + force_append_only='true' +) key encode json ; + +statement error +# The key encode is TEXT, but the primary key has 2 columns. The key encode TEXT requires the primary key to be a single column.s +create sink sink_text_error from t_kafka with ( + connector = 'kafka', + properties.bootstrap.server = 'message_queue:29092', + topic = 'test-rw-sink-text-key-id', + primary_key = 'id, v_varchar') +format plain encode json ( + force_append_only='true' +) key encode text ; + +statement error +# The key encode is TEXT, but the primary key column v_bytea has type bytea. The key encode TEXT requires the primary key column to be of type varchar, bool, small int, int, or big int. +create sink sink_text_error from t_kafka with ( + connector = 'kafka', + properties.bootstrap.server = 'message_queue:29092', + topic = 'test-rw-sink-text-key-id', + primary_key = 'v_bytea') +format plain encode json ( + force_append_only='true' +) key encode text ; + +statement ok +create sink sink_text_id from t_kafka with ( + connector = 'kafka', + properties.bootstrap.server = 'message_queue:29092', + topic = 'test-rw-sink-text-key-id', + primary_key = 'id') +format plain encode json ( + force_append_only='true' +) key encode text ; + +statement ok +create table t_sink_text_id (id int) +include key as rw_key +with ( + connector = 'kafka', + properties.bootstrap.server = 'message_queue:29092', + topic = 'test-rw-sink-text-key-id', +) format plain encode json; + +#====== + statement ok insert into t_kafka values (1, '8DfUFencLe', 31031, 1872, 1872, 26261.416, 23956.39329760601, '2023-04-14 06:27:14.104742', '\x00', '0 second', '0001-01-01', '00:00:01.123456', '0001-01-01 00:00:00.123456'::timestamptz, '{}'), @@ -193,7 +251,6 @@ insert into t_kafka values (6, 'V4y71v4Gip', 4014, 10844, 28842, 5885.368, 11210.458724794062, '2023-04-13 10:42:02.137742', '\xCAFEBABE', '4 hour', '0001-01-01', '00:00:01.123456', '0001-01-01 00:00:00.123456'::timestamptz, '{}'), (7, 'YIVLnWxHyf', 10324, 12652, 15914, 3946.7434, 10967.182297153104, '2023-04-14 04:41:03.083742', '\xBABEC0DE', '3 day', '0001-01-01', '01:00:01.123456', '0001-01-01 00:00:00.123456'::timestamptz, '{}'); - statement error create sink si_kafka_without_snapshot as select * from t_kafka with ( connector = 'kafka', @@ -216,8 +273,24 @@ create sink si_kafka_without_snapshot from t_kafka with ( snapshot = 'false', ); +sleep 2s + +query T +select rw_key from t_sink_text_id order by rw_key +---- +\x31 +\x32 +\x33 +\x34 +\x35 +\x36 +\x37 + statement ok insert into t_kafka values (8, 'lv7Eq3g8hx', 194, 19036, 28641, 13652.073, 993.408963466774, '2023-04-13 13:52:09.356742', '\xDEADBABE', '04:00:00.1234', '1970-01-01', '00:00:01.123456', '0001-01-01 00:00:00.123456'::timestamptz, '{}'), (9, 'nwRq4zejSQ', 10028, 20090, 24837, 20699.559, 11615.276406159757, '2023-04-13 12:40:42.487742', '\xDEADBABE', '05:01:00.123456', '1970-01-01', '00:00:01.123456', '0001-01-01 00:00:00.123456'::timestamptz, '{}'), - (10, '0oVqRIHqkb', 26951, 20674, 20674, 19387.238, 9042.404483827515, '2023-04-13 16:40:58.888742', '\x00', '05:01:00.1234567', '1970-01-01', '00:00:01.123456', '1970-01-01 00:00:00.123456'::timestamptz, '{}'); \ No newline at end of file + (10, '0oVqRIHqkb', 26951, 20674, 20674, 19387.238, 9042.404483827515, '2023-04-13 16:40:58.888742', '\x00', '05:01:00.1234567', '1970-01-01', '00:00:01.123456', '1970-01-01 00:00:00.123456'::timestamptz, '{}'); + +statement ok +drop table t_sink_text_id; diff --git a/e2e_test/sink/kafka/debezium.py b/e2e_test/sink/kafka/debezium.py index 4ec5e31281d88..5dbac1ae80216 100644 --- a/e2e_test/sink/kafka/debezium.py +++ b/e2e_test/sink/kafka/debezium.py @@ -11,9 +11,9 @@ # debezium sink sends k/v pair kv = line.split() key = json.loads(kv[0]) - # kafka consumer outputs string "null" for null payload - if kv[1] == "null": - value = kv[1] + # rpk output nothing for null payload + if len(kv) == 1: + value = None else: value = json.loads(kv[1]) # The `ts_ms` field may vary, so we delete it from the json object @@ -26,12 +26,10 @@ with open(test_output_file) as file: for line in file: kv = line.split() - if len(kv) != 2: - print(line) - assert(len(kv) == 2) key = json.loads(kv[0]) - if kv[1] == "null": - value = kv[1] + # rpk output nothing for null payload + if len(kv) == 1: + value = None else: value = json.loads(kv[1]) # Assert `ts_ms` is an integer here. diff --git a/e2e_test/sink/kafka/debezium3.result b/e2e_test/sink/kafka/debezium3.result index 196037c88b33b..b2d0235c2cfd2 100644 --- a/e2e_test/sink/kafka/debezium3.result +++ b/e2e_test/sink/kafka/debezium3.result @@ -1,5 +1,5 @@ {"payload":{"id":10},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"RisingWave.dev.t_kafka.Key","optional":false,"type":"struct"}} {"payload":{"after":{"id":10,"v_bigint":20674,"v_bytea":"AA==","v_date":0,"v_double":9042.404483827513,"v_float":19387.23828125,"v_integer":20674,"v_interval":"P0Y0M0DT5H1M0.123457S","v_jsonb":"{}","v_smallint":26951,"v_time":1000,"v_timestamp":1681404058888,"v_timestamptz":"1970-01-01T00:00:00.123456Z","v_varchar":"0oVqRIHqkb"},"before":null,"op":"c","source":{"db":"dev","table":"t_kafka","ts_ms":1700210120041},"ts_ms":1700210120041},"schema":{"fields":[{"field":"before","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"after","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"source","fields":[{"field":"db","optional":false,"type":"string"},{"field":"table","optional":true,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Source","optional":false,"type":"struct"},{"field":"op","optional":false,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Envelope","optional":false,"type":"struct"}} -{"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"RisingWave.dev.t_kafka.Key","optional":false,"type":"struct"}} null +{"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"RisingWave.dev.t_kafka.Key","optional":false,"type":"struct"}} {"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"RisingWave.dev.t_kafka.Key","optional":false,"type":"struct"}} {"payload":{"after":null,"before":{"id":1,"v_bigint":0,"v_bytea":"AA==","v_date":-719162,"v_double":0.0,"v_float":0.0,"v_integer":0,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":0,"v_time":1000,"v_timestamp":0,"v_timestamptz":"0001-01-01T00:00:00.123456Z","v_varchar":""},"op":"d","source":{"db":"dev","table":"t_kafka","ts_ms":1700210132903},"ts_ms":1700210132903},"schema":{"fields":[{"field":"before","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"after","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"source","fields":[{"field":"db","optional":false,"type":"string"},{"field":"table","optional":true,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Source","optional":false,"type":"struct"},{"field":"op","optional":false,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Envelope","optional":false,"type":"struct"}} {"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"RisingWave.dev.t_kafka.Key","optional":false,"type":"struct"}} {"payload":{"after":{"id":1,"v_bigint":0,"v_bytea":"AA==","v_date":-719162,"v_double":0.0,"v_float":0.0,"v_integer":0,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":0,"v_time":1000,"v_timestamp":0,"v_timestamptz":"0001-01-01T00:00:00.123456Z","v_varchar":""},"before":{"id":1,"v_bigint":1872,"v_bytea":"AA==","v_date":-719162,"v_double":23956.39329760601,"v_float":26261.416015625,"v_integer":1872,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":31031,"v_time":1000,"v_timestamp":1681453634104,"v_timestamptz":"0001-01-01T00:00:00.123456Z","v_varchar":"8DfUFencLe"},"op":"u","source":{"db":"dev","table":"t_kafka","ts_ms":1700210127905},"ts_ms":1700210127905},"schema":{"fields":[{"field":"before","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"after","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"source","fields":[{"field":"db","optional":false,"type":"string"},{"field":"table","optional":true,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Source","optional":false,"type":"struct"},{"field":"op","optional":false,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Envelope","optional":false,"type":"struct"}} {"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"RisingWave.dev.t_kafka.Key","optional":false,"type":"struct"}} {"payload":{"after":{"id":1,"v_bigint":1872,"v_bytea":"AA==","v_date":-719162,"v_double":23956.39329760601,"v_float":26261.416015625,"v_integer":1872,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":31031,"v_time":1000,"v_timestamp":1681453634104,"v_timestamptz":"0001-01-01T00:00:00.123456Z","v_varchar":"8DfUFencLe"},"before":null,"op":"c","source":{"db":"dev","table":"t_kafka","ts_ms":1700210120041},"ts_ms":1700210120041},"schema":{"fields":[{"field":"before","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"after","fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","name":"org.apache.kafka.connect.data.Timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","name":"io.debezium.time.Interval","optional":true,"type":"string"},{"field":"v_date","name":"org.apache.kafka.connect.data.Date","optional":true,"type":"int32"},{"field":"v_time","name":"org.apache.kafka.connect.data.Time","optional":true,"type":"int64"},{"field":"v_timestamptz","name":"io.debezium.time.ZonedTimestamp","optional":true,"type":"string"},{"field":"v_jsonb","name":"io.debezium.data.Json","optional":true,"type":"string"}],"name":"RisingWave.dev.t_kafka.Key","optional":true,"type":"struct"},{"field":"source","fields":[{"field":"db","optional":false,"type":"string"},{"field":"table","optional":true,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Source","optional":false,"type":"struct"},{"field":"op","optional":false,"type":"string"},{"field":"ts_ms","optional":false,"type":"int64"}],"name":"RisingWave.dev.t_kafka.Envelope","optional":false,"type":"struct"}} diff --git a/e2e_test/sink/kafka/drop_sink.slt b/e2e_test/sink/kafka/drop_sink.slt index db599e80ec6d1..7bd370bcb1264 100644 --- a/e2e_test/sink/kafka/drop_sink.slt +++ b/e2e_test/sink/kafka/drop_sink.slt @@ -13,5 +13,8 @@ drop sink si_kafka_upsert_schema; statement ok drop sink si_kafka_without_snapshot; +statement ok +drop sink sink_text_id; + statement ok drop table t_kafka; diff --git a/e2e_test/sink/kafka/protobuf.slt b/e2e_test/sink/kafka/protobuf.slt index 0c74cc8a0b369..61a91435567da 100644 --- a/e2e_test/sink/kafka/protobuf.slt +++ b/e2e_test/sink/kafka/protobuf.slt @@ -1,3 +1,6 @@ +statement ok +set sink_decouple = false; + statement ok create table from_kafka with ( connector = 'kafka', @@ -13,7 +16,7 @@ create table from_kafka_csr_trivial with ( topic = 'test-rw-sink-append-only-protobuf-csr-a', properties.bootstrap.server = 'message_queue:29092') format plain encode protobuf ( - schema.registry = 'http://message_queue:8081', + schema.registry = 'http://schemaregistry:8082', message = 'test.package.MessageA'); statement ok @@ -22,7 +25,7 @@ create table from_kafka_csr_nested with ( topic = 'test-rw-sink-append-only-protobuf-csr-hi', properties.bootstrap.server = 'message_queue:29092') format plain encode protobuf ( - schema.registry = 'http://message_queue:8081', + schema.registry = 'http://schemaregistry:8082', message = 'test.package.MessageH.MessageI'); statement ok @@ -68,7 +71,7 @@ create sink sink_csr_trivial as select string_field as field_a from into_kafka w properties.bootstrap.server = 'message_queue:29092') format plain encode protobuf ( force_append_only = true, - schema.registry = 'http://message_queue:8081', + schema.registry = 'http://schemaregistry:8082', message = 'test.package.MessageA'); statement ok @@ -78,7 +81,7 @@ create sink sink_csr_nested as select sint32_field as field_i from into_kafka wi properties.bootstrap.server = 'message_queue:29092') format plain encode protobuf ( force_append_only = true, - schema.registry = 'http://message_queue:8081', + schema.registry = 'http://schemaregistry:8082', message = 'test.package.MessageH.MessageI'); sleep 2s diff --git a/e2e_test/sink/kafka/upsert3.result b/e2e_test/sink/kafka/upsert3.result index dde36e5f444eb..fe845aff27301 100644 --- a/e2e_test/sink/kafka/upsert3.result +++ b/e2e_test/sink/kafka/upsert3.result @@ -1,5 +1,5 @@ {"id":10} {"id":10,"v_bigint":20674,"v_bytea":"AA==","v_date":719163,"v_double":9042.404483827513,"v_float":19387.23828125,"v_integer":20674,"v_interval":"P0Y0M0DT5H1M0.123457S","v_jsonb":"{}","v_smallint":26951,"v_time":1000,"v_timestamp":1681404058888,"v_timestamptz":"1970-01-01 00:00:00.123456","v_varchar":"0oVqRIHqkb"} -{"id":1} null +{"id":1} {"id":1} {"id":1,"v_bigint":0,"v_bytea":"AA==","v_date":1,"v_double":0.0,"v_float":0.0,"v_integer":0,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":0,"v_time":1000,"v_timestamp":0,"v_timestamptz":"0001-01-01 00:00:00.123456","v_varchar":""} {"id":1} {"id":1,"v_bigint":1872,"v_bytea":"AA==","v_date":1,"v_double":23956.39329760601,"v_float":26261.416015625,"v_integer":1872,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":31031,"v_time":1000,"v_timestamp":1681453634104,"v_timestamptz":"0001-01-01 00:00:00.123456","v_varchar":"8DfUFencLe"} {"id":2} {"id":2,"v_bigint":4598,"v_bytea":"AA==","v_date":719163,"v_double":31923.077305746086,"v_float":27031.224609375,"v_integer":4598,"v_interval":"P0Y0M0DT4H0M0S","v_jsonb":"{}","v_smallint":22690,"v_time":1000,"v_timestamp":1681429444869,"v_timestamptz":"0001-01-01 00:00:00.123456","v_varchar":"sIo1XXVeHZ"} diff --git a/e2e_test/sink/kafka/upsert_schema3.result b/e2e_test/sink/kafka/upsert_schema3.result index 18675dcbb76d3..ccbb7fe542b37 100644 --- a/e2e_test/sink/kafka/upsert_schema3.result +++ b/e2e_test/sink/kafka/upsert_schema3.result @@ -1,5 +1,5 @@ {"payload":{"id":10},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} {"payload":{"id":10,"v_bigint":20674,"v_bytea":"AA==","v_date":719163,"v_double":9042.404483827513,"v_float":19387.23828125,"v_integer":20674,"v_interval":"P0Y0M0DT5H1M0.123457S","v_jsonb":"{}","v_smallint":26951,"v_time":1000,"v_timestamp":1681404058888,"v_timestamptz":"1970-01-01T00:00:00.123456Z","v_varchar":"0oVqRIHqkb"},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","optional":true,"type":"string"},{"field":"v_date","optional":true,"type":"int32"},{"field":"v_time","optional":true,"type":"int64"},{"field":"v_timestamptz","optional":true,"type":"string"},{"field":"v_jsonb","optional":true,"type":"string"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} -{"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} null +{"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} {"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} {"payload":{"id":1,"v_bigint":0,"v_bytea":"AA==","v_date":1,"v_double":0.0,"v_float":0.0,"v_integer":0,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":0,"v_time":1000,"v_timestamp":0,"v_timestamptz":"0001-01-01T00:00:00.123456Z","v_varchar":""},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","optional":true,"type":"string"},{"field":"v_date","optional":true,"type":"int32"},{"field":"v_time","optional":true,"type":"int64"},{"field":"v_timestamptz","optional":true,"type":"string"},{"field":"v_jsonb","optional":true,"type":"string"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} {"payload":{"id":1},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} {"payload":{"id":1,"v_bigint":1872,"v_bytea":"AA==","v_date":1,"v_double":23956.39329760601,"v_float":26261.416015625,"v_integer":1872,"v_interval":"P0Y0M0DT0H0M0S","v_jsonb":"{}","v_smallint":31031,"v_time":1000,"v_timestamp":1681453634104,"v_timestamptz":"0001-01-01T00:00:00.123456Z","v_varchar":"8DfUFencLe"},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","optional":true,"type":"string"},{"field":"v_date","optional":true,"type":"int32"},{"field":"v_time","optional":true,"type":"int64"},{"field":"v_timestamptz","optional":true,"type":"string"},{"field":"v_jsonb","optional":true,"type":"string"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} {"payload":{"id":2},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} {"payload":{"id":2,"v_bigint":4598,"v_bytea":"AA==","v_date":719163,"v_double":31923.077305746086,"v_float":27031.224609375,"v_integer":4598,"v_interval":"P0Y0M0DT4H0M0S","v_jsonb":"{}","v_smallint":22690,"v_time":1000,"v_timestamp":1681429444869,"v_timestamptz":"0001-01-01T00:00:00.123456Z","v_varchar":"sIo1XXVeHZ"},"schema":{"fields":[{"field":"id","optional":true,"type":"int32"},{"field":"v_varchar","optional":true,"type":"string"},{"field":"v_smallint","optional":true,"type":"int16"},{"field":"v_integer","optional":true,"type":"int32"},{"field":"v_bigint","optional":true,"type":"int64"},{"field":"v_float","optional":true,"type":"float"},{"field":"v_double","optional":true,"type":"double"},{"field":"v_timestamp","optional":true,"type":"int64"},{"field":"v_bytea","optional":true,"type":"bytes"},{"field":"v_interval","optional":true,"type":"string"},{"field":"v_date","optional":true,"type":"int32"},{"field":"v_time","optional":true,"type":"int64"},{"field":"v_timestamptz","optional":true,"type":"string"},{"field":"v_jsonb","optional":true,"type":"string"}],"name":"dev.t_kafka","optional":false,"type":"struct"}} diff --git a/e2e_test/sink/mongodb_sink.slt b/e2e_test/sink/mongodb_sink.slt new file mode 100644 index 0000000000000..2122993e3003a --- /dev/null +++ b/e2e_test/sink/mongodb_sink.slt @@ -0,0 +1,106 @@ +statement ok +create table t1( + a smallint, + b int, + c bigint, + d rw_int256, + e real, + f double precision, + g varchar, + h bytea, + i date, + j time, + k timestamp, + l timestamptz, + m interval, + n STRUCT , d INTEGER>, + o varchar[], + p jsonb +) append only; + +statement ok +create sink t1_sink from t1 +with ( + connector='mongodb', + type = 'append-only', + mongodb.url = 'mongodb://mongodb:27017/?replicaSet=rs0', + collection.name = 'demo.t1', + mongodb.bulk_write.max_entries = '1024' +); + +statement ok +insert into t1 values(1, 2, 3, 4, 5.0, 6.0, '7', '\xDe00BeEf', date '2022-04-08', time '18:20:49', + '2022-03-13 01:00:00'::timestamp, '2022-03-13 01:00:00Z'::timestamptz, interval '4 hour', + ROW(ROW(8), 9), ARRAY['a', 'b', 'c'], '{"a": [{"b": 1}], "c": true}'::jsonb); + +statement ok +create table t2( + _id bigint primary key, + collection_name varchar, + value varchar +); + +statement ok +create sink t2_sink from t2 +with ( + connector='mongodb', + type = 'upsert', + mongodb.url = 'mongodb://mongodb:27017/?replicaSet=rs0', + collection.name = 'demo.t2', + mongodb.bulk_write.max_entries = '1024', + collection.name.field = 'collection_name', + collection.name.field.drop = 'true', + primary_key='_id' +); + +statement ok +insert into t2 values(1, 'shard_2024_01.tenant_1', 'data'); + +statement ok +insert into t2 values(2, '', 'data'); + +statement ok +create table t3( + a int, + b int, + value text, + primary key (a,b) +); + +statement ok +create sink t3_sink from t3 +with ( + connector='mongodb', + type = 'upsert', + mongodb.url = 'mongodb://mongodb:27017/?replicaSet=rs0', + collection.name = 'demo.t3', + mongodb.bulk_write.max_entries = '1024', + primary_key='a,b' +); + +statement ok +delete from t3 where a = 1 and b = 2; + +statement ok +insert into t3 values(1, 2, 'abc'); + +statement ok +FLUSH; + +statement ok +DROP SINK t1_sink; + +statement ok +DROP TABLE t1; + +statement ok +DROP SINK t2_sink; + +statement ok +DROP TABLE t2; + +statement ok +DROP SINK t3_sink; + +statement ok +DROP TABLE t3; \ No newline at end of file diff --git a/e2e_test/sink/mqtt_sink.slt b/e2e_test/sink/mqtt_sink.slt index d19addb024c97..2602d2ddc6198 100644 --- a/e2e_test/sink/mqtt_sink.slt +++ b/e2e_test/sink/mqtt_sink.slt @@ -1,3 +1,6 @@ +statement ok +set sink_decouple = false; + statement ok CREATE TABLE mqtt ( device_id varchar, diff --git a/e2e_test/sink/pulsar_sink.slt b/e2e_test/sink/pulsar_sink.slt index 2284f9c1877e3..f8d6aa1aaff4c 100644 --- a/e2e_test/sink/pulsar_sink.slt +++ b/e2e_test/sink/pulsar_sink.slt @@ -1,3 +1,6 @@ +statement ok +set sink_decouple = false; + statement ok CREATE TABLE pulsar ( id BIGINT, diff --git a/e2e_test/sink/redis_cluster_sink.slt b/e2e_test/sink/redis_cluster_sink.slt index 5d2d84b773367..03d197485777a 100644 --- a/e2e_test/sink/redis_cluster_sink.slt +++ b/e2e_test/sink/redis_cluster_sink.slt @@ -14,13 +14,7 @@ FROM )FORMAT PLAIN ENCODE JSON(force_append_only='true'); statement ok -INSERT INTO t6 VALUES (1, 1); - -statement ok -INSERT INTO t6 VALUES (2, 2); - -statement ok -INSERT INTO t6 VALUES (3, 3); +INSERT INTO t6 VALUES (1, 1),(2, 2),(3, 3),(4, 4),(5, 5),(6, 6),(7, 7),(8, 8),(9, 9),(10, 10),(11, 11),(12, 12),(13, 13),(14, 14),(15, 15); statement ok FLUSH; diff --git a/e2e_test/sink/remote/jdbc.check.pg.slt b/e2e_test/sink/remote/jdbc.check.pg.slt index 0c29969445d2f..1ec8c827d939b 100644 --- a/e2e_test/sink/remote/jdbc.check.pg.slt +++ b/e2e_test/sink/remote/jdbc.check.pg.slt @@ -44,3 +44,9 @@ query IT select * from t1_uuid; ---- 221 74605c5a-a7bb-4b3b-8742-2a12e9709dea hello world + + +query TIT +select * from sk_t1_uuid +---- +21189447-8736-44bd-b254-26b5dec91da9 2 bb diff --git a/e2e_test/sink/remote/jdbc.load.slt b/e2e_test/sink/remote/jdbc.load.slt index a453c76feeb70..9a4ede4e032ed 100644 --- a/e2e_test/sink/remote/jdbc.load.slt +++ b/e2e_test/sink/remote/jdbc.load.slt @@ -164,6 +164,26 @@ CREATE SINK s1_uuid FROM t1_uuid WITH ( statement ok INSERT INTO t1_uuid values (221, '74605c5a-a7bb-4b3b-8742-2a12e9709dea', 'hello world'); + +statement ok +CREATE TABLE t1_test_uuid_delete (id varchar, v1 int, v2 varchar, primary key(id, v2)); + +statement ok +INSERT INTO t1_test_uuid_delete VALUES ('fb48ecc1-917f-4f4b-ab6d-d8e37809caf8', 1, 'aa'), ('21189447-8736-44bd-b254-26b5dec91da9', 2, 'bb'); + +statement ok +CREATE SINK sk_t1_uuid FROM t1_test_uuid_delete WITH ( + connector='jdbc', + jdbc.url='jdbc:postgresql://db:5432/test?user=test&password=connector', + table.name='sk_t1_uuid', + primary_key='id, v2', + type='upsert' +); + +statement ok +DELETE FROM t1_test_uuid_delete WHERE id='fb48ecc1-917f-4f4b-ab6d-d8e37809caf8' AND v2='aa'; + + statement ok INSERT INTO tt2 VALUES (1), diff --git a/e2e_test/sink/remote/pg_create_table.sql b/e2e_test/sink/remote/pg_create_table.sql index 3677dbb067131..ee272ef747a7a 100644 --- a/e2e_test/sink/remote/pg_create_table.sql +++ b/e2e_test/sink/remote/pg_create_table.sql @@ -83,3 +83,5 @@ CREATE TABLE biz.t_types ( CREATE TABLE biz.t2 ( "aBc" INTEGER PRIMARY KEY ); + +CREATE TABLE sk_t1_uuid (id uuid, v1 int, v2 varchar, primary key(id, v2)); diff --git a/e2e_test/sink/sink_into_table/basic.slt b/e2e_test/sink/sink_into_table/basic.slt index 179f659e4c113..59e43773560f0 100644 --- a/e2e_test/sink/sink_into_table/basic.slt +++ b/e2e_test/sink/sink_into_table/basic.slt @@ -327,7 +327,6 @@ drop table t_primary_key; statement ok drop table t_s3; - # multi sinks statement ok @@ -423,3 +422,42 @@ drop table t_c; statement ok drop table t_m; + +# from view + +statement ok +create table t_a(v int); + +statement ok +insert into t_a values (1), (2), (3); + +statement ok +create view v_a as select v from t_a; + +statement ok +create table t_m(v int primary key); + +statement ok +create sink s_a into t_m as select v from v_a; + +statement ok +flush; + +query I +select * from t_m order by v; +---- +1 +2 +3 + +statement ok +drop sink s_a; + +statement ok +drop view v_a; + +statement ok +drop table t_m; + +statement ok +drop table t_a; diff --git a/e2e_test/sink/sink_into_table/issue_17278.slt b/e2e_test/sink/sink_into_table/issue_17278.slt new file mode 100644 index 0000000000000..a1d61d33b3ce2 --- /dev/null +++ b/e2e_test/sink/sink_into_table/issue_17278.slt @@ -0,0 +1,34 @@ +statement ok +SET RW_IMPLICIT_FLUSH TO true; + +statement ok +create table t( + x int, + y timestamptz default now(), + z timestamptz default now() - interval '1 minute' +) append only; + +statement ok +create table s(x int) append only; + +statement ok +create sink ss into t from s with (type = 'append-only'); + +statement ok +insert into s values (1), (2); + +query IT rowsort +select x from t where y >= date '2021-01-01'; +---- +1 +2 + +statement ok +drop sink ss; + +statement ok +drop table t; + +statement ok +drop table s; + diff --git a/e2e_test/sink/sink_into_table/specify_column.slt b/e2e_test/sink/sink_into_table/specify_column.slt new file mode 100644 index 0000000000000..2eefd8abf8fb0 --- /dev/null +++ b/e2e_test/sink/sink_into_table/specify_column.slt @@ -0,0 +1,54 @@ +statement ok +SET RW_IMPLICIT_FLUSH TO true; + +statement ok +create table s (a int, b int, c int) append only; + +statement ok +create table t (a int, b int default 900, c int default 9000); + +statement error +create sink ss into t(aaa) as select a from s with(type = 'append-only'); + +statement error +create sink ss into t(a) as select a, b from s with(type = 'append-only'); + +statement error +create sink ss into t(a, b) as select b from s with(type = 'append-only'); + +statement error +create sink ss into t(a, b, c, a) as select a, b from s with(type = 'append-only'); + +statement ok +create sink s1 into t(a,B,c) as select c, b, a from s with(type = 'append-only'); + +statement ok +create sink s2 into t(a,B) as select 2*c, 2*b from s with(type = 'append-only'); + +statement ok +create sink s3 into t(c) as select 3*a from s with(type = 'append-only'); + +statement ok +insert into s values(10, 100, 1000); + +query III rowsort +select * from t order by a; +---- +1000 100 10 +2000 200 9000 +NULL 900 30 + +statement ok +drop sink s1; + +statement ok +drop sink s2; + +statement ok +drop sink s3; + +statement ok +drop table s; + +statement ok +drop table t; diff --git a/e2e_test/sink/sqlserver_sink.slt b/e2e_test/sink/sqlserver_sink.slt new file mode 100644 index 0000000000000..156b8b865ffc8 --- /dev/null +++ b/e2e_test/sink/sqlserver_sink.slt @@ -0,0 +1,59 @@ +statement ok +create table t_many_data_type_rw ( + k1 int, k2 int, + c_boolean bool, + c_int16 smallint, + c_int32 int, + c_int64 bigint, + c_float32 float, + c_float64 double, + c_decimal decimal, + c_date date, + c_time time, + c_timestamp timestamp, + c_timestampz timestamp, + c_nvarchar string, + c_varbinary bytea); + +statement ok +create sink s_many_data_type from t_many_data_type_rw with ( + connector = 'sqlserver', + type = 'upsert', + sqlserver.host = 'sqlserver-server', + sqlserver.port = 1433, + sqlserver.user = 'SA', + sqlserver.password = 'SomeTestOnly@SA', + sqlserver.database = 'SinkTest', + sqlserver.table = 't_many_data_type', + primary_key = 'k1,k2', +); + +statement ok +insert into t_many_data_type_rw values +(0,0,false,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL), +(1,1,false,1,1,1,1.0,1.0,1.0,date '2022-04-08',time '18:20:49','2022-03-13 01:00:00'::timestamp,'2022-03-13 01:00:00Z'::timestamptz,'Hello World!','\xDe00BeEf'), +(1,2,false,2,2,1,1.0,1.0,1.0,date '2022-04-08',time '18:20:49','2022-03-13 01:00:00'::timestamp,'2022-03-13 01:00:00Z'::timestamptz,'Hello World!','\xDe00BeEf'), +(1,3,false,2,2,1,1.0,1.0,1.0,date '2022-04-08',time '18:20:49','2022-03-13 01:00:00'::timestamp,'2022-03-13 01:00:00Z'::timestamptz,'Hello World!','\xDe00BeEf'), +(1,4,false,2,2,1,1.0,1.0,1.0,date '2022-04-08',time '18:20:49','2022-03-13 01:00:00'::timestamp,'2022-03-13 01:00:00Z'::timestamptz,'Hello World!','\xDe00BeEf'), +(1,1,false,2,2,1,1.0,1.0,1.0,date '2022-04-08',time '18:20:49','2022-03-13 01:00:00'::timestamp,'2022-03-13 01:00:00Z'::timestamptz,'Hello World!','\xDe00BeEf'); +flush; + +statement ok +delete from t_many_data_type_rw where k1=1 and k2=2; +delete from t_many_data_type_rw where k1=1 and k2=3; +flush; + +statement ok +insert into t_many_data_type_rw values +(1,1,false,55,55,1,1.0,1.0,1.0,date '2022-04-08',time '18:20:49','2022-03-13 01:00:00'::timestamp,'2022-03-13 01:00:00Z'::timestamptz,'Hello World!','\xDe00BeEf'), +(1,2,false,66,66,1,1.0,1.0,1.0,date '2022-04-08',time '18:20:49','2022-03-13 01:00:00'::timestamp,'2022-03-13 01:00:00Z'::timestamptz,'Hello World!','\xDe00BeEf'); +flush; + +statement ok +FLUSH; + +statement ok +DROP SINK s_many_data_type; + +statement ok +DROP TABLE t_many_data_type_rw; diff --git a/e2e_test/slow_tests/backfill/rate_limit/slow-udf.slt b/e2e_test/slow_tests/backfill/rate_limit/slow-udf.slt new file mode 100644 index 0000000000000..a2b1a6fc63130 --- /dev/null +++ b/e2e_test/slow_tests/backfill/rate_limit/slow-udf.slt @@ -0,0 +1,49 @@ +statement ok +create table t(v1 int); + +statement ok +insert into t select 2 from generate_series(1, 1000000); + +statement ok +set streaming_rate_limit=1; + +statement ok +set background_ddl=true; + +statement ok +CREATE FUNCTION delay(secs int) RETURNS int LANGUAGE python AS $$ +def delay(n): + import time + time.sleep(n) + return n +$$; + +statement ok +create sink m1 as select delay(v1) from t with (connector='blackhole'); + +statement ok +set background_ddl = false; + +statement ok +set streaming_rate_limit=default; + +statement ok +flush; + +statement ok +flush; + +statement ok +flush; + +statement ok +flush; + +statement ok +drop sink m1; + +statement ok +drop function delay; + +statement ok +drop table t; \ No newline at end of file diff --git a/e2e_test/udf/always_retry_python.slt b/e2e_test/slow_tests/udf/always_retry_python.slt similarity index 85% rename from e2e_test/udf/always_retry_python.slt rename to e2e_test/slow_tests/udf/always_retry_python.slt index 19e66dd60b07d..78bf926c32986 100644 --- a/e2e_test/udf/always_retry_python.slt +++ b/e2e_test/slow_tests/udf/always_retry_python.slt @@ -15,7 +15,7 @@ statement ok CREATE TABLE t (v1 int); statement ok -INSERT INTO t select 0 from generate_series(1, 30); +INSERT INTO t select 0 from generate_series(1, 60); statement ok flush; @@ -33,7 +33,7 @@ CREATE MATERIALIZED VIEW mv_no_retry AS SELECT sleep_no_retry(v1) as s1 from t; statement ok CREATE MATERIALIZED VIEW mv_always_retry AS SELECT sleep_always_retry(v1) as s1 from t; -# Immediately kill the server, sleep for 1minute. +# Immediately kill the server, sleep system ok pkill -9 -i python && sleep 60 @@ -50,10 +50,11 @@ SELECT count(*) FROM mv_always_retry where s1 is NULL; ---- 0 -query B -SELECT count(*) > 0 FROM mv_no_retry where s1 is NULL; ----- -t +# FIXME(kwannoel): Somehow this is flaky.. +# query B +# SELECT count(*) > 0 FROM mv_no_retry where s1 is NULL; +# ---- +# t statement ok SET STREAMING_RATE_LIMIT TO DEFAULT; diff --git a/e2e_test/source/README.md b/e2e_test/source/README.md index 4152ab3dc9737..611619cb13423 100644 --- a/e2e_test/source/README.md +++ b/e2e_test/source/README.md @@ -1,6 +1,9 @@ > [!NOTE] > > Please write new tests according to the style in `e2e_test/source_inline`. +> Don't add new tests here. +> +> See the [connector development guide](http://risingwavelabs.github.io/risingwave/connector/intro.html#end-to-end-tests) for more information about how to test. Test in this directory needs some prior setup. diff --git a/e2e_test/source/basic/alter/rate_limit_source_kafka.slt b/e2e_test/source/basic/alter/rate_limit_source_kafka.slt new file mode 100644 index 0000000000000..7d991083345a1 --- /dev/null +++ b/e2e_test/source/basic/alter/rate_limit_source_kafka.slt @@ -0,0 +1,130 @@ +############## Create kafka seed data + +statement ok +create table kafka_seed_data (v1 int); + +statement ok +insert into kafka_seed_data select * from generate_series(1, 1000); + +############## Sink into kafka + +statement ok +create sink kafka_sink +from + kafka_seed_data with ( + properties.bootstrap.server = 'message_queue:29092', + topic = 'kafka_source', + type = 'append-only', + force_append_only='true', + connector = 'kafka' +); + +############## Source from kafka (rate_limit = 0) + +# Wait for the topic to create +skipif in-memory +sleep 5s + +statement ok +create source kafka_source (v1 int) with ( + connector = 'kafka', + topic = 'kafka_source', + properties.bootstrap.server = 'message_queue:29092', + scan.startup.mode = 'earliest', +) FORMAT PLAIN ENCODE JSON + +statement ok +flush; + +############## Check data + +skipif in-memory +sleep 3s + +############## Create MV on source + +statement ok +SET STREAMING_RATE_LIMIT=0; + +statement ok +create materialized view rl_mv1 as select count(*) from kafka_source; + +statement ok +create materialized view rl_mv2 as select count(*) from kafka_source; + +statement ok +create materialized view rl_mv3 as select count(*) from kafka_source; + +statement ok +SET STREAMING_RATE_LIMIT=default; + +############## MVs should have 0 records, since source has (rate_limit = 0) + +statement ok +flush; + +query I +select * from rl_mv1; +---- +0 + +query I +select * from rl_mv2; +---- +0 + +query I +select * from rl_mv3; +---- +0 + +############## Alter Source (rate_limit = 0 --> rate_limit = 1000) + +skipif in-memory +query I +alter source kafka_source set streaming_rate_limit to 1000; + +skipif in-memory +query I +alter source kafka_source set streaming_rate_limit to default; + +skipif in-memory +sleep 3s + +skipif in-memory +query I +select count(*) > 0 from rl_mv1; +---- +t + +skipif in-memory +query I +select count(*) > 0 from rl_mv2; +---- +t + +skipif in-memory +query I +select count(*) > 0 from rl_mv3; +---- +t + +############## Cleanup + +statement ok +drop materialized view rl_mv1; + +statement ok +drop materialized view rl_mv2; + +statement ok +drop materialized view rl_mv3; + +statement ok +drop source kafka_source; + +statement ok +drop sink kafka_sink; + +statement ok +drop table kafka_seed_data; \ No newline at end of file diff --git a/e2e_test/source/basic/alter/rate_limit_table_kafka.slt b/e2e_test/source/basic/alter/rate_limit_table_kafka.slt new file mode 100644 index 0000000000000..bf1fd6672d6ea --- /dev/null +++ b/e2e_test/source/basic/alter/rate_limit_table_kafka.slt @@ -0,0 +1,88 @@ +############## Create kafka seed data + +statement ok +create table kafka_seed_data (v1 int); + +statement ok +insert into kafka_seed_data select * from generate_series(1, 1000); + +############## Sink into kafka + +statement ok +create sink kafka_sink +from + kafka_seed_data with ( + properties.bootstrap.server = 'message_queue:29092', + topic = 'kafka_source', + type = 'append-only', + force_append_only='true', + connector = 'kafka' +); + +############## Source from kafka (rate_limit = 0) + +statement ok +create table kafka_source (v1 int) with ( + connector = 'kafka', + topic = 'kafka_source', + properties.bootstrap.server = 'message_queue:29092', + scan.startup.mode = 'earliest', + streaming_rate_limit = 0 +) FORMAT PLAIN ENCODE JSON + +statement ok +flush; + +############## Check data + +skipif in-memory +sleep 3s + +skipif in-memory +query I +select count(*) from kafka_source; +---- +0 + +############## Can still insert data when rate limit = 0 + +statement ok +insert into kafka_source values(1); + +statement ok +flush; + +query I +select count(*) from kafka_source; +---- +1 + +############## Alter source (rate_limit = 0 --> rate_limit = 1000) + +skipif in-memory +query I +alter table kafka_source set streaming_rate_limit to 1000; + +skipif in-memory +query I +alter table kafka_source set streaming_rate_limit to default; + +skipif in-memory +sleep 3s + +skipif in-memory +query I +select count(*) > 1 from kafka_source; +---- +t + +############## Cleanup + +statement ok +drop table kafka_source; + +statement ok +drop sink kafka_sink; + +statement ok +drop table kafka_seed_data; \ No newline at end of file diff --git a/e2e_test/source/basic/datagen.slt b/e2e_test/source/basic/datagen.slt index 91c51f624a1ea..d89850538a3fd 100644 --- a/e2e_test/source/basic/datagen.slt +++ b/e2e_test/source/basic/datagen.slt @@ -186,9 +186,9 @@ statement ok drop table s1; # Do NOT allow With clause to contain a comma only. -statement error Expected identifier.* +statement error expected identifier.* create table s1 (v1 int) with (,) FORMAT PLAIN ENCODE JSON; # Do NOT allow an empty With clause. -statement error Expected identifier.* +statement error expected identifier.* create table s1 (v1 int) with () FORMAT PLAIN ENCODE JSON; diff --git a/e2e_test/source/basic/ddl.slt b/e2e_test/source/basic/ddl.slt index 402cf129b86ba..a56d90934c149 100644 --- a/e2e_test/source/basic/ddl.slt +++ b/e2e_test/source/basic/ddl.slt @@ -4,8 +4,9 @@ create source s; db error: ERROR: Failed to run the query Caused by: - sql parser error: Expected description of the format, found: ; at line:1, column:17 -Near "create source s" + sql parser error: expected description of the format, found: ; +LINE 1: create source s; + ^ statement error missing WITH clause @@ -187,7 +188,7 @@ create table s ( query T show sources ---- - +s query T show tables diff --git a/e2e_test/source/basic/inlcude_key_as.slt b/e2e_test/source/basic/inlcude_key_as.slt deleted file mode 100644 index c971e1ac074d0..0000000000000 --- a/e2e_test/source/basic/inlcude_key_as.slt +++ /dev/null @@ -1,133 +0,0 @@ -# upsert format must have a pk -statement error -CREATE TABLE upsert_students_default_key ( - "ID" INT, - "firstName" VARCHAR, - "lastName" VARCHAR, - age INT, - height REAL, - weight REAL -) -INCLUDE KEY AS rw_key -WITH ( - connector = 'kafka', - properties.bootstrap.server = 'message_queue:29092', - topic = 'upsert_json') -FORMAT UPSERT ENCODE JSON - -# upsert format pk must be the key column -statement error -CREATE TABLE upsert_students_default_key ( - "ID" INT primary key, - "firstName" VARCHAR, - "lastName" VARCHAR, - age INT, - height REAL, - weight REAL -) -INCLUDE KEY AS rw_key -WITH ( - connector = 'kafka', - properties.bootstrap.server = 'message_queue:29092', - topic = 'upsert_json') -FORMAT UPSERT ENCODE JSON - -statement ok -CREATE TABLE upsert_students_default_key ( - "ID" INT, - "firstName" VARCHAR, - "lastName" VARCHAR, - age INT, - height REAL, - weight REAL, -) -INCLUDE KEY AS rw_key -WITH ( - connector = 'kafka', - properties.bootstrap.server = 'message_queue:29092', - topic = 'upsert_json') -FORMAT PLAIN ENCODE JSON - -statement ok -create table additional_columns (a int) -include key as key_col -include partition as partition_col -include offset as offset_col -include timestamp as timestamp_col -include header as header_col -WITH ( - connector = 'kafka', - properties.bootstrap.server = 'message_queue:29092', - topic = 'kafka_additional_columns') -FORMAT PLAIN ENCODE JSON - -# header with varchar type & non-exist header key -statement ok -create table additional_columns_1 (a int) -include key as key_col -include partition as partition_col -include offset as offset_col -include timestamp as timestamp_col -include header 'header1' as header_col_1 -include header 'header2' as header_col_2 -include header 'header2' varchar as header_col_3 -include header 'header3' as header_col_4 -WITH ( - connector = 'kafka', - properties.bootstrap.server = 'message_queue:29092', - topic = 'kafka_additional_columns') -FORMAT PLAIN ENCODE JSON - -statement ok -select * from upsert_students_default_key; - -statement ok -select * from additional_columns; - -statement ok -select * from additional_columns_1; - -# Wait enough time to ensure SourceExecutor consumes all Kafka data. -sleep 3s - -query I -select count(rw_key) from upsert_students_default_key ----- -15 - -query I -SELECT count(*) -FROM additional_columns -WHERE key_col IS NOT NULL - AND partition_col IS NOT NULL - AND offset_col IS NOT NULL - AND timestamp_col IS NOT NULL - AND header_col IS NOT NULL ----- -101 - -# the input data is from scripts/source/prepare_ci_kafka.sh -# ``` -# for i in {0..100}; do echo "key$i:{\"a\": $i}" | ${KCAT_BIN} -P -b message_queue:29092 -t ${ADDI_COLUMN_TOPIC} -K : -H "header1=v1" -H "header2=v2"; done -# ``` -# The command generates 101 messages with key `key0` to `key100` and value `{"a": 0}` to `{"a": 100}`, with fixed headers `header1=v1` and `header2=v2`. - -query TT -SELECT (header_col[1]).key AS key, (header_col[1]).value::text AS value -FROM additional_columns limit 1; ----- -header1 \x7631 - -query TTTT -select header_col_1, header_col_2, header_col_3, header_col_4 from additional_columns_1 limit 1 ----- -\x7631 \x7632 v2 NULL - -statement ok -drop table upsert_students_default_key - -statement ok -drop table additional_columns - -statement ok -drop table additional_columns_1 diff --git a/e2e_test/source/basic/kafka.slt b/e2e_test/source/basic/kafka.slt index 08c84947846a7..dee5de1cbb539 100644 --- a/e2e_test/source/basic/kafka.slt +++ b/e2e_test/source/basic/kafka.slt @@ -166,7 +166,7 @@ create table s8_no_schema_field ( statement error without schema registry create table s9 with ( connector = 'kafka', - topic = 'avro_bin', + topic = 'avro_simple_schema_bin', properties.bootstrap.server = 'message_queue:29092', scan.startup.mode = 'earliest' ) FORMAT PLAIN ENCODE AVRO (schema.location = 'file:///risingwave/avro-simple-schema.avsc'); @@ -174,7 +174,7 @@ create table s9 with ( statement ok create table s9 with ( connector = 'kafka', - topic = 'avro_bin', + topic = 'avro_simple_schema_bin', properties.bootstrap.server = 'message_queue:29092', scan.startup.mode = 'earliest' ) FORMAT PLAIN ENCODE AVRO (schema.location = 'file:///risingwave/avro-simple-schema.avsc', with_deprecated_file_header = true); @@ -182,7 +182,7 @@ create table s9 with ( statement ok create table s10 with ( connector = 'kafka', - topic = 'avro_c_bin', + topic = 'avro_complex_schema_bin', properties.bootstrap.server = 'message_queue:29092', scan.startup.mode = 'earliest' ) FORMAT PLAIN ENCODE AVRO (schema.location = 'file:///risingwave/avro-complex-schema.avsc', with_deprecated_file_header = true); @@ -287,7 +287,7 @@ FORMAT PLAIN ENCODE PROTOBUF ( statement ok create source s18 with ( connector = 'kafka', - topic = 'avro_c_bin', + topic = 'avro_complex_schema_bin', properties.bootstrap.server = 'message_queue:29092', scan.startup.mode = 'earliest' ) FORMAT PLAIN ENCODE AVRO (schema.location = 'file:///risingwave/avro-complex-schema.avsc', with_deprecated_file_header = true); diff --git a/e2e_test/source/basic/kafka_shared_source.slt b/e2e_test/source/basic/kafka_shared_source.slt deleted file mode 100644 index 5245d6ea68630..0000000000000 --- a/e2e_test/source/basic/kafka_shared_source.slt +++ /dev/null @@ -1,58 +0,0 @@ -control substitution on - -statement ok -SET rw_enable_shared_source TO true; - -statement ok -create source s0 (v1 int, v2 varchar) with ( - connector = 'kafka', - topic = 'kafka_4_partition_topic', - properties.bootstrap.server = '${KAFKA_BOOTSTRAP_SERVER:message_queue:29092}', - scan.startup.mode = 'earliest' -) FORMAT PLAIN ENCODE JSON; - -statement ok -create materialized view mv_1 as select * from s0; - -statement ok -SET rw_enable_shared_source TO false; - -statement ok -create materialized view mv_2 as select * from s0; - -statement ok -flush; - -# Wait enough time to ensure SourceExecutor consumes all Kafka data. -sleep 1s - -query IT rowsort -select v1, v2 from s0; ----- -1 1 -2 22 -3 333 -4 4444 - -query IT rowsort -select v1, v2 from mv_1; ----- -1 1 -2 22 -3 333 -4 4444 - -query IT rowsort -select v1, v2 from mv_2; ----- -1 1 -2 22 -3 333 -4 4444 - -# TODO: add more data to the topic and re-check the data. Currently there's no good test infra to do this... -# To test the correctness of source backfill, we might need to keep producing data during an interval, to let it go -# through the backfill stage to the forward stage. - -statement ok -drop source s0 cascade; diff --git a/e2e_test/source/basic/nosim_kafka.slt b/e2e_test/source/basic/nosim_kafka.slt index bc4a88e65ee71..b773126c9a7c1 100644 --- a/e2e_test/source/basic/nosim_kafka.slt +++ b/e2e_test/source/basic/nosim_kafka.slt @@ -1,62 +1,59 @@ -# Start with nosim to avoid running in deterministic test +control substitution on +# FIXME: does this really work?? +# Start with nosim to avoid running in deterministic test -# If we cannot extract key schema, use message key as varchar primary key -statement ok -CREATE TABLE upsert_avro_json_default_key ( primary key (rw_key) ) -INCLUDE KEY AS rw_key -WITH ( - connector = 'kafka', - properties.bootstrap.server = 'message_queue:29092', - topic = 'upsert_avro_json') -FORMAT UPSERT ENCODE AVRO (schema.registry = 'http://message_queue:8081'); statement ok CREATE TABLE upsert_student_avro_json ( primary key (rw_key) ) INCLUDE KEY AS rw_key WITH ( connector = 'kafka', - properties.bootstrap.server = 'message_queue:29092', + properties.bootstrap.server = '${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}', topic = 'upsert_student_avro_json') -FORMAT UPSERT ENCODE AVRO (schema.registry = 'http://message_queue:8081'); - - -# TODO: Uncomment this when we add test data kafka key with format `"ID":id` -# statement ok -# CREATE TABLE upsert_avro_json ( -# PRIMARY KEY("ID") -# ) -# WITH ( -# connector = 'kafka', -# properties.bootstrap.server = 'message_queue:29092', -# topic = 'upsert_avro_json') -# FORMAT UPSERT ENCODE AVRO (schema.registry = 'http://message_queue:8081'); +FORMAT UPSERT ENCODE AVRO (schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}'); statement ok CREATE TABLE debezium_non_compact (PRIMARY KEY(order_id)) with ( connector = 'kafka', kafka.topic = 'debezium_non_compact_avro_json', - kafka.brokers = 'message_queue:29092', + kafka.brokers = '${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}', kafka.scan.startup.mode = 'earliest' -) FORMAT DEBEZIUM ENCODE AVRO (schema.registry = 'http://message_queue:8081'); +) FORMAT DEBEZIUM ENCODE AVRO (schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}'); statement ok CREATE TABLE debezium_compact (PRIMARY KEY(order_id)) with ( connector = 'kafka', kafka.topic = 'debezium_compact_avro_json', - kafka.brokers = 'message_queue:29092', + kafka.brokers = '${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}', kafka.scan.startup.mode = 'earliest' -) FORMAT DEBEZIUM ENCODE AVRO (schema.registry = 'http://message_queue:8081'); +) FORMAT DEBEZIUM ENCODE AVRO (schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}'); statement ok CREATE TABLE kafka_json_schema_plain with ( connector = 'kafka', kafka.topic = 'kafka_json_schema', - kafka.brokers = 'kafka:9093', + kafka.brokers = '${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}', kafka.scan.startup.mode = 'earliest' -) FORMAT PLAIN ENCODE JSON (schema.registry = 'http://schemaregistry:8082'); +) FORMAT PLAIN ENCODE JSON (schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}'); + + +query +describe kafka_json_schema_plain; +---- +dimensions (empty) false NULL +map jsonb false NULL +notMap (empty) false NULL +price double precision false NULL +productId bigint false NULL +productName character varying false NULL +tags character varying[] false NULL +_row_id serial true NULL +primary key _row_id NULL NULL +distribution key _row_id NULL NULL +table description kafka_json_schema_plain NULL NULL statement ok CREATE TABLE kafka_json_schema_upsert (PRIMARY KEY(rw_key)) @@ -64,9 +61,9 @@ INCLUDE KEY AS rw_key with ( connector = 'kafka', kafka.topic = 'kafka_upsert_json_schema', - kafka.brokers = 'kafka:9093', + kafka.brokers = '${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}', kafka.scan.startup.mode = 'earliest' -) FORMAT UPSERT ENCODE JSON (schema.registry = 'http://schemaregistry:8082'); +) FORMAT UPSERT ENCODE JSON (schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}'); statement ok flush; @@ -74,32 +71,6 @@ flush; # Wait enough time to ensure SourceExecutor consumes all Kafka data. sleep 8s -query II -SELECT - op_type, "ID", "CLASS_ID", "ITEM_ID", "ATTR_ID", "ATTR_VALUE", "ORG_ID", "UNIT_INFO", "UPD_TIME", "DEC_VAL" -FROM - upsert_avro_json_default_key -ORDER BY - "ID"; ----- -update id1 -1 6768 6970 value9 7172 info9 2021-05-18T07:59:58.714Z -21474836.47 -delete id2 2 7778 7980 value10 8182 info10 2021-05-19T15:22:45.539Z 99999999.99 -delete id3 3 7778 7980 value10 8182 info10 2021-05-19T15:22:45.539Z 21474836.47 -delete id5 5 7778 7980 value10 8182 info10 2021-05-19T15:22:45.539Z 21474836.49 - -# query II -# SELECT -# * -# FROM -# upsert_avro_json -# ORDER BY -# "ID"; -# ---- -# update id1 -1 6768 6970 value9 7172 info9 2021-05-18T07:59:58.714Z -# delete id2 2 7778 7980 value10 8182 info10 2021-05-19T15:22:45.539Z -# delete id3 3 7778 7980 value10 8182 info10 2021-05-19T15:22:45.539Z -# delete id5 5 7778 7980 value10 8182 info10 2021-05-19T15:22:45.539Z - query II SELECT "ID", "firstName", "lastName", "age", "height", "weight" @@ -128,10 +99,10 @@ select count(*) from debezium_compact; query TFITT select - "dimensions", "price", "productId", "productName", "tags" + * from kafka_json_schema_plain ---- -(9.5,7,12) 12.5 1 An ice sculpture {cold,ice} +(9.5,7,12) {"foo": "bar"} (b) 12.5 1 An ice sculpture {cold,ice} query TFITT select @@ -141,12 +112,6 @@ from kafka_json_schema_upsert order by id (9.5,7,12) 1 23 An ice sculpture {cold,ice} (9.5,7,12) 2 12.5 An ice sculpture {cold,ice} -statement ok -DROP TABLE upsert_avro_json_default_key; - -# statement ok -# DROP TABLE upsert_avro_json; - statement ok DROP TABLE upsert_student_avro_json; @@ -160,4 +125,4 @@ statement ok DROP TABLE kafka_json_schema_plain; statement ok -DROP TABLE kafka_json_schema_upsert; \ No newline at end of file +DROP TABLE kafka_json_schema_upsert; diff --git a/e2e_test/source/basic/old_row_format_syntax/datagen.slt b/e2e_test/source/basic/old_row_format_syntax/datagen.slt index 267ae8eff4c66..6467b624d0dbb 100644 --- a/e2e_test/source/basic/old_row_format_syntax/datagen.slt +++ b/e2e_test/source/basic/old_row_format_syntax/datagen.slt @@ -182,9 +182,9 @@ statement ok drop table s1; # Do NOT allow With clause to contain a comma only. -statement error Expected identifier.* +statement error expected identifier.* create table s1 (v1 int) with (,) ROW FORMAT JSON; # Do NOT allow an empty With clause. -statement error Expected identifier.* +statement error expected identifier.* create table s1 (v1 int) with () ROW FORMAT JSON; diff --git a/e2e_test/source/basic/old_row_format_syntax/ddl.slt b/e2e_test/source/basic/old_row_format_syntax/ddl.slt index d5c41d4ded878..b48249ca7e393 100644 --- a/e2e_test/source/basic/old_row_format_syntax/ddl.slt +++ b/e2e_test/source/basic/old_row_format_syntax/ddl.slt @@ -142,7 +142,7 @@ create table s ( query T show sources ---- - +s query T show tables diff --git a/e2e_test/source/basic/old_row_format_syntax/kafka.slt b/e2e_test/source/basic/old_row_format_syntax/kafka.slt index 6c387aeb4523f..1f4c118f30dc5 100644 --- a/e2e_test/source/basic/old_row_format_syntax/kafka.slt +++ b/e2e_test/source/basic/old_row_format_syntax/kafka.slt @@ -158,7 +158,7 @@ create table s8_no_schema_field ( statement error without schema registry create table s9 with ( connector = 'kafka', - topic = 'avro_bin', + topic = 'avro_simple_schema_bin', properties.bootstrap.server = 'message_queue:29092', scan.startup.mode = 'earliest' ) row format avro row schema location 'file:///risingwave/avro-simple-schema.avsc' @@ -166,7 +166,7 @@ create table s9 with ( statement error without schema registry create table s10 with ( connector = 'kafka', - topic = 'avro_c_bin', + topic = 'avro_complex_schema_bin', properties.bootstrap.server = 'message_queue:29092', scan.startup.mode = 'earliest' ) row format avro row schema location 'file:///risingwave/avro-complex-schema.avsc' @@ -265,7 +265,7 @@ create source s17 with ( statement error without schema registry create source s18 with ( connector = 'kafka', - topic = 'avro_c_bin', + topic = 'avro_complex_schema_bin', properties.bootstrap.server = 'message_queue:29092', scan.startup.mode = 'earliest' ) row format avro row schema location 'file:///risingwave/avro-complex-schema.avsc' diff --git a/e2e_test/source/basic/pubsub.slt b/e2e_test/source/basic/pubsub.slt deleted file mode 100644 index b245d9b2aea89..0000000000000 --- a/e2e_test/source/basic/pubsub.slt +++ /dev/null @@ -1,79 +0,0 @@ -# fail with invalid emulator_host -statement error -CREATE TABLE s1 (v1 int, v2 varchar) WITH ( - pubsub.subscription = 'test-subscription-1', - pubsub.emulator_host = 'invalid_host:5981' -) FORMAT PLAIN ENCODE JSON; - -statement ok -CREATE TABLE s1 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', - pubsub.subscription = 'test-subscription-1', - pubsub.emulator_host = 'localhost:5980' -) FORMAT PLAIN ENCODE JSON; - -statement ok -SELECT * FROM s1; - -statement ok -DROP TABLE s1; - -# fail with invalid subscription -statement error -CREATE TABLE s2 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', - pubsub.subscription = 'test-subscription-not-2', - pubsub.emulator_host = 'localhost:5980' -) FORMAT PLAIN ENCODE JSON; - -statement ok -CREATE TABLE s2 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', - pubsub.subscription = 'test-subscription-2', - pubsub.emulator_host = 'localhost:5980' -) FORMAT PLAIN ENCODE JSON; - -# fail if both start_offset and start_snapshot are provided -statement error -CREATE TABLE s3 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', - pubsub.subscription = 'test-subscription-3', - pubsub.emulator_host = 'localhost:5980', - pubsub.start_offset = "121212", - pubsub.start_snapshot = "snapshot-that-doesnt-exist" -) FORMAT PLAIN ENCODE JSON; - -# wait for source -sleep 10s - -# flush data into storage -statement ok -flush; - -query IT rowsort -select v1, v2 FROM s2; ----- -0 name5 -0 name9 -1 name0 -1 name7 -2 name0 -2 name3 -3 name2 -3 name9 -4 name6 -4 name7 -5 name3 -5 name8 -6 name3 -6 name4 -7 name0 -7 name5 -8 name8 -8 name9 -9 name2 -9 name2 - -statement ok -DROP TABLE s2; - diff --git a/e2e_test/source/basic/schema_registry.slt b/e2e_test/source/basic/schema_registry.slt deleted file mode 100644 index 76f867b2b1d0e..0000000000000 --- a/e2e_test/source/basic/schema_registry.slt +++ /dev/null @@ -1,67 +0,0 @@ -# wrong strategy name -statement error -create source s1 () with ( - connector = 'kafka', - topic = 'upsert_avro_json-record', - properties.bootstrap.server = 'message_queue:29092' -) format plain encode avro ( - schema.registry = 'http://message_queue:8081', - schema.registry.name.strategy = 'no sense', - message = 'CPLM.OBJ_ATTRIBUTE_VALUE', -); - -# redundant field key.message -statement error -create source s1 () with ( - connector = 'kafka', - topic = 'upsert_avro_json-record', - properties.bootstrap.server = 'message_queue:29092' -) format plain encode avro ( - schema.registry = 'http://message_queue:8081', - schema.registry.name.strategy = 'record_name_strategy', - message = 'CPLM.OBJ_ATTRIBUTE_VALUE', - key.message = 'string' -); - -statement ok -create source s1 () with ( - connector = 'kafka', - topic = 'upsert_avro_json-record', - properties.bootstrap.server = 'message_queue:29092' -) format plain encode avro ( - schema.registry = 'http://message_queue:8081', - schema.registry.name.strategy = 'record_name_strategy', - message = 'CPLM.OBJ_ATTRIBUTE_VALUE', -); - -# lack field key.message -statement error -create table t1 () with ( - connector = 'kafka', - topic = 'upsert_avro_json-topic-record', - properties.bootstrap.server = 'message_queue:29092' -) format upsert encode avro ( - schema.registry = 'http://message_queue:8081', - schema.registry.name.strategy = 'topic_record_name_strategy', - message = 'CPLM.OBJ_ATTRIBUTE_VALUE' -); - -statement ok -create table t1 (primary key(rw_key)) -INCLUDE KEY AS rw_key -with ( - connector = 'kafka', - topic = 'upsert_avro_json-topic-record', - properties.bootstrap.server = 'message_queue:29092' -) format upsert encode avro ( - schema.registry = 'http://message_queue:8081', - schema.registry.name.strategy = 'topic_record_name_strategy', - message = 'CPLM.OBJ_ATTRIBUTE_VALUE', - key.message = 'string' -); - -statement ok -drop source s1; - -statement ok -drop table t1; diff --git a/e2e_test/source/basic/ttl_table_with_con.slt b/e2e_test/source/basic/ttl_table_with_con.slt new file mode 100644 index 0000000000000..6a232bbdee2a2 --- /dev/null +++ b/e2e_test/source/basic/ttl_table_with_con.slt @@ -0,0 +1,32 @@ +statement ok +create table t (v1 int, v2 varchar) APPEND ONLY with ( + connector = 'kafka', + topic = 'kafka_1_partition_topic', + properties.bootstrap.server = 'message_queue:29092', + scan.startup.mode = 'earliest', + retention_seconds = 5 +) FORMAT PLAIN ENCODE JSON; + +statement ok +flush; + +# Wait enough time to ensure SourceExecutor consumes all Kafka data. +sleep 1s + +query IT rowsort +select * from t +---- +1 1 +2 22 +3 333 +4 4444 + +statement ok +select pg_sleep(10); + +query I +select * from t; +---- + +statement ok +drop table t; \ No newline at end of file diff --git a/e2e_test/source/cdc/cdc.check.slt b/e2e_test/source/cdc/cdc.check.slt index b6fb7f2156fbf..3d6ea0e179391 100644 --- a/e2e_test/source/cdc/cdc.check.slt +++ b/e2e_test/source/cdc/cdc.check.slt @@ -69,3 +69,9 @@ select * from numeric_to_rw_int256 order by id; 5 NULL 6 NULL 7 NULL + +query II +select * from enum_to_varchar order by id; +---- +1 happy +2 ok diff --git a/e2e_test/source/cdc/cdc.check_new_rows.slt b/e2e_test/source/cdc/cdc.check_new_rows.slt index e3e52c1e35d7e..cac4896f3fa58 100644 --- a/e2e_test/source/cdc/cdc.check_new_rows.slt +++ b/e2e_test/source/cdc/cdc.check_new_rows.slt @@ -15,7 +15,7 @@ select cnt from shipments_cnt; 4 query ITTTT -select * from person_new order by id; +SELECT id,name,email_address,credit_card,city from person_new order by id; ---- 1000 vicky noris yplkvgz@qbxfg.com 7878 5821 1864 2539 cheyenne 1001 peter white myckhsp@xpmpe.com 1781 2313 8157 6974 boise @@ -68,7 +68,7 @@ SELECT * from orders_test_cnt 5 query ITT -SELECT * FROM rw.products_test order by id limit 3 +SELECT id,name,description FROM rw.products_test order by id limit 3 ---- 101 RW Small 2-wheel scooter 102 RW 12V car battery @@ -112,4 +112,212 @@ select * from numeric_to_rw_int256 order by id; 104 NULL 105 NULL 106 NULL -107 NULL \ No newline at end of file +107 NULL + +query II +select * from enum_to_varchar order by id; +---- +1 happy +2 ok +3 sad + +query II +select id, my_int from list_with_null_shared order by id; +---- +1 {1,2,NULL} +2 {NULL,3,4} +3 {NULL,-3,-4} +4 {-4,-5,-6} +5 NULL +6 NULL + +# my_num: varchar[] +query II +select id, my_num from list_with_null_shared order by id; +---- +1 NULL +2 {2.2,0,NULL} +3 NULL +4 {NULL,-99999999999999999.9999} +5 NULL +6 NULL + +# my_num1: numeric[] +query II +select id, my_num_1 from list_with_null_shared order by id; +---- +1 NULL +2 {2.2,0,NULL} +3 NULL +4 {NULL,-99999999999999999.9999} +5 NULL +6 NULL + +# my_num2: rw_int256[] +query II +select id, my_num_2 from list_with_null_shared order by id; +---- +1 NULL +2 NULL +3 NULL +4 NULL +5 NULL +6 NULL + +# Due to the bug in Debezium, if a enum list contains `NULL`, the list will be converted to `NULL` +query II +select id, my_mood from list_with_null_shared order by id; +---- +1 NULL +2 {happy,ok,sad} +3 NULL +4 NULL +5 NULL +6 NULL + +query II +select id, my_uuid from list_with_null_shared order by id; +---- +1 {bb488f9b-330d-4012-b849-12adeb49e57e,bb488f9b-330d-4012-b849-12adeb49e57f,NULL} +2 {2de296df-eda7-4202-a81f-1036100ef4f6,2977afbc-0b12-459c-a36f-f623fc9e9840} +3 {NULL,471acecf-a4b4-4ed3-a211-7fb2291f159f,9bc35adf-fb11-4130-944c-e7eadb96b829} +4 {b2e4636d-fa03-4ad4-bf16-029a79dca3e2} +5 NULL +6 NULL + +query II +select id, my_bytea from list_with_null_shared order by id; +---- +1 {"\\x00","\\x01",NULL} +2 {"\\x00","\\x01","\\x02"} +3 {NULL,"\\x99","\\xaa"} +4 {"\\x88","\\x99","\\xaa"} +5 NULL +6 NULL + +query TTTTTTT +SELECT c_boolean, c_smallint, c_integer, c_bigint, c_decimal, c_real, c_double_precision +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +f -32767 -2147483647 -9223372036854775807 -10.0 -10000 -10000 +f 0 0 0 0 0 0 +f NULL NULL 1 NULL NULL NULL +f 1 123 1234567890 123.45 123.45 123.456 +t -32767 -2147483647 -9223372036854775807 -10.0 -10000 -10000 +t 0 0 0 0 0 0 +t NULL NULL 1 NULL NULL NULL +t 1 123 1234567890 123.45 123.45 123.456 + +query TTTTTTT +SELECT c_varchar, c_bytea +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +d \x00 +(empty) \x00 +NULL NULL +example \xdeadbeef +d \x00 +(empty) \x00 +NULL NULL +example \xdeadbeef + +query TTTTTTT +SELECT c_date, c_time, c_timestamp, c_timestamptz, c_interval +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +0001-01-01 00:00:00 2001-01-01 00:00:00 2001-01-01 08:00:00+00:00 00:00:00 +0001-01-01 00:00:00 2001-01-01 00:00:00 2001-01-01 08:00:00+00:00 00:00:00 +0024-05-19 NULL NULL NULL NULL +0024-01-01 12:34:56 2024-05-19 12:34:56 2024-05-19 12:34:56+00:00 1 day +0001-01-01 00:00:00 2001-01-01 00:00:00 2001-01-01 08:00:00+00:00 00:00:00 +0001-01-01 00:00:00 2001-01-01 00:00:00 2001-01-01 08:00:00+00:00 00:00:00 +0024-05-19 NULL NULL NULL NULL +0024-01-01 12:34:56 2024-05-19 12:34:56 2024-05-19 12:34:56+00:00 1 day + +query TTTTTTT +SELECT c_jsonb, c_uuid, c_enum +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +{} bb488f9b-330d-4012-b849-12adeb49e57e happy +{} NULL sad +NULL NULL NULL +{"key": "value"} 123e4567-e89b-12d3-a456-426614174000 happy +{} bb488f9b-330d-4012-b849-12adeb49e57e happy +{} NULL sad +NULL NULL NULL +{"key": "value"} 123e4567-e89b-12d3-a456-426614174000 happy + +query TTTTTTT +SELECT c_boolean_array, c_smallint_array, c_integer_array, c_bigint_array +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +{f} {-32767} {-2147483647} {-9223372036854775807} +{} {} {} {} +NULL NULL NULL NULL +{NULL,t} {NULL,1} {NULL,123} {NULL,1234567890} +{f} {-32767} {-2147483647} {-9223372036854775807} +{} {} {} {} +NULL NULL NULL NULL +{NULL,t} {NULL,1} {NULL,123} {NULL,1234567890} + + +query TTTTTTT +SELECT c_decimal_array, c_real_array, c_double_precision_array +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +{-10.0} {-10000} {-10000} +{} {} {} +NULL NULL NULL +{NULL,123.45} {NULL,123.45} {NULL,123.456} +{-10.0} {-10000} {-10000} +{} {} {} +NULL NULL NULL +{NULL,123.45} {NULL,123.45} {NULL,123.456} + +query TTTTTTT +SELECT c_varchar_array, c_bytea_array +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +{""} {"\\x00"} +{} {} +NULL NULL +{NULL,example} {NULL,"\\xdeadbeef"} +{""} {"\\x00"} +{} {} +NULL NULL +{NULL,example} {NULL,"\\xdeadbeef"} + +query TTTTTTT +SELECT c_date_array, c_time_array, c_timestamp_array, c_timestamptz_array, c_interval_array +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +{0001-01-01} {00:00:00} {"2001-01-01 00:00:00"} {"2001-01-01 08:00:00+00:00"} NULL +{} {} {} {} NULL +NULL NULL NULL NULL NULL +{NULL,2024-05-19} {NULL,12:34:56} {NULL,"2024-05-19 12:34:56"} {NULL,"2024-05-19 12:34:56+00:00"} NULL +{0001-01-01} {00:00:00} {"2001-01-01 00:00:00"} {"2001-01-01 08:00:00+00:00"} NULL +{} {} {} {} NULL +NULL NULL NULL NULL NULL +{NULL,2024-05-19} {NULL,12:34:56} {NULL,"2024-05-19 12:34:56"} {NULL,"2024-05-19 12:34:56+00:00"} NULL + +query TTTTTTT +SELECT c_jsonb_array, c_uuid_array, c_enum_array +FROM postgres_all_types +ORDER BY c_boolean, c_bigint, c_date; +---- +{"{}"} {bb488f9b-330d-4012-b849-12adeb49e57e} NULL +{} {} {} +NULL NULL NULL +{NULL,"{\"key\": \"value\"}"} {NULL,123e4567-e89b-12d3-a456-426614174000} NULL +{"{}"} {bb488f9b-330d-4012-b849-12adeb49e57e} NULL +{} {} {} +NULL NULL NULL +{NULL,"{\"key\": \"value\"}"} {NULL,123e4567-e89b-12d3-a456-426614174000} NULL diff --git a/e2e_test/source/cdc/cdc.create_source_job.slt b/e2e_test/source/cdc/cdc.create_source_job.slt deleted file mode 100644 index 1f2300087110b..0000000000000 --- a/e2e_test/source/cdc/cdc.create_source_job.slt +++ /dev/null @@ -1,15 +0,0 @@ -control substitution on - -# create a cdc source job, which format fixed to `FORMAT PLAIN ENCODE JSON` -statement ok -create source mysql_source with ( - connector = 'mysql-cdc', - hostname = '${MYSQL_HOST:localhost}', - port = '${MYSQL_TCP_PORT:8306}', - username = 'rwcdc', - password = '${MYSQL_PWD:}', - database.name = 'mytest', - server.id = '5001' -); - -sleep 2s diff --git a/e2e_test/source/cdc/cdc.load.slt b/e2e_test/source/cdc/cdc.load.slt index 1cff579bb054d..2c372cbd3ffdd 100644 --- a/e2e_test/source/cdc/cdc.load.slt +++ b/e2e_test/source/cdc/cdc.load.slt @@ -161,7 +161,7 @@ create table shipments_2 ( # Test user-provided publication statement ok create table t1_rw ( - v1 int primary key, + "V1" int primary key, v3 varchar ) with ( connector = 'postgres-cdc', @@ -226,3 +226,19 @@ create table numeric_to_varchar ( table.name = 'numeric_table', slot.name = 'numeric_to_varchar' ); + +statement ok +create table enum_to_varchar ( + id int, + current_mood varchar, + PRIMARY KEY (id) +) with ( + connector = 'postgres-cdc', + hostname = '${PGHOST:localhost}', + port = '${PGPORT:5432}', + username = '${PGUSER:$USER}', + password = '${PGPASSWORD:}', + database.name = '${PGDATABASE:postgres}', + table.name = 'enum_table', + slot.name = 'enum_to_varchar' +); diff --git a/e2e_test/source/cdc/cdc.share_stream.slt b/e2e_test/source/cdc/cdc.share_stream.slt index f1a301baed800..3dc26d98c6282 100644 --- a/e2e_test/source/cdc/cdc.share_stream.slt +++ b/e2e_test/source/cdc/cdc.share_stream.slt @@ -27,10 +27,9 @@ statement error Should not create MATERIALIZED VIEW or SELECT directly on shared create materialized view mv as select * from mysql_mytest; statement error The upstream table name must contain database name prefix* -create table products_test ( id INT, +create table products_test ( id INT PRIMARY KEY, name STRING, - description STRING, - PRIMARY KEY (id) + description STRING ) from mysql_mytest table 'products'; statement ok @@ -41,7 +40,13 @@ create table rw.products_test ( id INT, name STRING, description STRING, PRIMARY KEY (id) -) from mysql_mytest table 'mytest.products'; +) include timestamp as commit_ts +include database_name as database_name +include table_name as table_name +from mysql_mytest table 'mytest.products'; + +# sleep to ensure (default,'Milk','Milk is a white liquid food') is consumed from Debezium message instead of backfill. +sleep 10s system ok mysql --protocol=tcp -u root mytest -e "INSERT INTO products VALUES (default,'Milk','Milk is a white liquid food'); @@ -145,12 +150,33 @@ SELECT * from orders_test_cnt 4 query ITT -SELECT * FROM rw.products_test order by id limit 3 +SELECT id,name,description FROM rw.products_test order by id limit 3 ---- 101 scooter Small 2-wheel scooter 102 car battery 12V car battery 103 12-pack drill bits 12-pack of drill bits with sizes ranging from #40 to #3 +query TT +select database_name, table_name from rw.products_test limit 3; +---- +mytest products +mytest products +mytest products + + +# commit_ts of historical records should be '1970-01-01 00:00:00+00:00' +query I +SELECT count(*) as cnt from rw.products_test where commit_ts = '1970-01-01 00:00:00+00:00' +---- +9 + +# commit_ts of new records should greater than '1970-01-01 00:00:00+00:00' +query TTT +SELECT name,description FROM rw.products_test where commit_ts > '1970-01-01 00:00:00+00:00' order by id +---- +Milk Milk is a white liquid food +Juice 100ml Juice + query ITTT SELECT order_id,order_date,customer_name,product_id FROM orders_test order by order_id limit 3 ---- @@ -193,6 +219,8 @@ CREATE TABLE IF NOT EXISTS postgres_all_types( c_timestamptz timestamptz, c_interval interval, c_jsonb jsonb, + c_uuid varchar, + c_enum varchar, c_boolean_array boolean[], c_smallint_array smallint[], c_integer_array integer[], @@ -208,18 +236,18 @@ CREATE TABLE IF NOT EXISTS postgres_all_types( c_timestamptz_array timestamptz[], c_interval_array interval[], c_jsonb_array jsonb[], - c_uuid varchar, + c_uuid_array varchar[], + c_enum_array varchar[], PRIMARY KEY (c_boolean,c_bigint,c_date) ) from pg_source table 'public.postgres_all_types'; statement error The upstream table name must contain schema name prefix* CREATE TABLE person_new ( - id int, + id int PRIMARY KEY, name varchar, email_address varchar, credit_card varchar, - city varchar, - PRIMARY KEY (id) + city varchar ) FROM pg_source TABLE 'person'; statement ok @@ -230,7 +258,11 @@ CREATE TABLE person_new ( credit_card varchar, city varchar, PRIMARY KEY (id) -) FROM pg_source TABLE 'public.person'; +) INCLUDE TIMESTAMP AS commit_ts +INCLUDE DATABASE_NAME as database_name +INCLUDE SCHEMA_NAME as schema_name +INCLUDE TABLE_NAME as table_name +FROM pg_source TABLE 'public.person'; statement ok CREATE MATERIALIZED VIEW person_new_cnt AS SELECT COUNT(*) AS cnt FROM person_new; @@ -240,7 +272,7 @@ sleep 3s query TTTTTTT SELECT c_boolean,c_date,c_time,c_timestamp,c_jsonb,c_smallint_array,c_timestamp_array,c_uuid FROM postgres_all_types where c_bigint=-9223372036854775807 ---- -f 0001-01-01 00:00:00 0001-01-01 00:00:00 {} {-32767} {"0001-01-01 00:00:00"} bb488f9b-330d-4012-b849-12adeb49e57e +f 0001-01-01 00:00:00 2001-01-01 00:00:00 {} {-32767} {"2001-01-01 00:00:00"} bb488f9b-330d-4012-b849-12adeb49e57e # postgres streaming test @@ -259,12 +291,36 @@ SELECT * from person_new_cnt ---- 6 +query TTT +SELECT database_name,schema_name,table_name from person_new limit 3; +---- +cdc_test public person +cdc_test public person +cdc_test public person + + +query ITTTT +SELECT id,name,email_address,credit_card,city from person_new order by id; +---- +1000 vicky noris yplkvgz@qbxfg.com 7878 5821 1864 2539 cheyenne +1001 peter white myckhsp@xpmpe.com 1781 2313 8157 6974 boise +1002 sarah spencer wipvdbm@dkaap.com 3453 4987 9481 6270 los angeles +1100 noris ypl@qbxfg.com 1864 2539 enne +1101 white myc@xpmpe.com 8157 6974 se +1102 spencer wip@dkaap.com 9481 6270 angeles + +# historical data query ITTTT -SELECT * from person_new order by id; +SELECT id,name,email_address,credit_card,city from person_new where commit_ts = '1970-01-01 00:00:00+00:00' order by id; ---- 1000 vicky noris yplkvgz@qbxfg.com 7878 5821 1864 2539 cheyenne 1001 peter white myckhsp@xpmpe.com 1781 2313 8157 6974 boise 1002 sarah spencer wipvdbm@dkaap.com 3453 4987 9481 6270 los angeles + +# incremental data +query ITTTT +SELECT id,name,email_address,credit_card,city from person_new where commit_ts > '1970-01-01 00:00:00+00:00' order by id; +---- 1100 noris ypl@qbxfg.com 1864 2539 enne 1101 white myc@xpmpe.com 8157 6974 se 1102 spencer wip@dkaap.com 9481 6270 angeles @@ -283,6 +339,13 @@ CREATE TABLE numeric_to_varchar_shared ( PRIMARY KEY (id) ) FROM pg_source TABLE 'public.numeric_table'; +statement ok +CREATE TABLE numeric_to_numeric_shared ( + id int, + num numeric, + PRIMARY KEY (id) +) FROM pg_source TABLE 'public.numeric_table'; + statement ok CREATE TABLE numeric_list_to_rw_int256_list_shared ( id int, @@ -297,6 +360,33 @@ CREATE TABLE numeric_list_to_varchar_list_shared ( PRIMARY KEY (id) ) FROM pg_source TABLE 'public.numeric_list'; +statement ok +CREATE TABLE numeric_list_to_numeric_list_shared ( + id int, + num numeric[], + PRIMARY KEY (id) +) FROM pg_source TABLE 'public.numeric_list'; + +statement ok +CREATE TABLE enum_to_varchar_shared ( + id int, + current_mood varchar, + PRIMARY KEY (id) +) FROM pg_source TABLE 'public.enum_table'; + + +statement ok +CREATE TABLE list_with_null_shared ( + id int, + my_int int[], + my_num varchar[], + my_num_1 numeric[], + my_num_2 rw_int256[], + my_mood varchar[], + my_uuid varchar[], + my_bytea bytea[], + PRIMARY KEY (id) +) FROM pg_source TABLE 'public.list_with_null'; system ok psql -c " @@ -308,6 +398,9 @@ insert into numeric_table values(104, 115792089237316195423570985008687907853269 insert into numeric_table values(105, 115792089237316195423570985008687907853269984665640564039457584007913129639936.555555); insert into numeric_table values(106, 'NaN'::numeric); insert into numeric_table values(107, 'Infinity'::numeric); +INSERT INTO enum_table VALUES (2, 'ok'); +insert into numeric_list values(3, '{3.14, 6, 57896044618658097711785492504343953926634992332820282019728792003956564819967, 57896044618658097711785492504343953926634992332820282019728792003956564819968, 115792089237316195423570985008687907853269984665640564039457584007913129639936.555555}'); +INSERT INTO numeric_list values(4, '{nan, infinity, 524596}'); " sleep 3s @@ -347,6 +440,23 @@ select * from numeric_to_rw_int256_shared order by id; 106 NULL 107 NULL +query II +select * from numeric_to_numeric_shared order by id; +---- +1 3.14 +2 NULL +3 NULL +4 NULL +5 NULL +6 NaN +7 Infinity +102 NULL +103 NULL +104 NULL +105 NULL +106 NaN +107 Infinity + system ok psql -c " DELETE FROM numeric_table WHERE id IN (102, 103, 104, 105, 106, 107); @@ -356,10 +466,28 @@ query II select * from numeric_list_to_varchar_list_shared order by id; ---- 1 {3.14,6,57896044618658097711785492504343953926634992332820282019728792003956564819967,57896044618658097711785492504343953926634992332820282019728792003956564819968,115792089237316195423570985008687907853269984665640564039457584007913129639936.555555} -2 {NAN,POSITIVE_INFINITY,NEGATIVE_INFINITY} +2 NULL +3 {3.14,6,57896044618658097711785492504343953926634992332820282019728792003956564819967,57896044618658097711785492504343953926634992332820282019728792003956564819968,115792089237316195423570985008687907853269984665640564039457584007913129639936.555555} +4 NULL query II select * from numeric_list_to_rw_int256_list_shared order by id; ---- -1 {NULL,6,57896044618658097711785492504343953926634992332820282019728792003956564819967,NULL,NULL} -2 {NULL,NULL,NULL} \ No newline at end of file +1 NULL +2 NULL +3 NULL +4 NULL + +query II +select * from numeric_list_to_numeric_list_shared order by id; +---- +1 NULL +2 NULL +3 NULL +4 NULL + +query II +select * from enum_to_varchar_shared order by id; +---- +1 happy +2 ok diff --git a/e2e_test/source/cdc/cdc.validate.postgres.slt b/e2e_test/source/cdc/cdc.validate.postgres.slt index 1cf42983de49a..1392357298fc3 100644 --- a/e2e_test/source/cdc/cdc.validate.postgres.slt +++ b/e2e_test/source/cdc/cdc.validate.postgres.slt @@ -1,4 +1,5 @@ # CDC Postgres source validate test +control substitution on # invalid username statement error @@ -201,3 +202,44 @@ explain create table numeric_to_varchar ( table.name = 'numeric_table', slot.name = 'numeric_to_varchar' ); + +statement ok +explain create table enum_to_varchar ( + id int, + current_mood varchar, + PRIMARY KEY (id) +) with ( + connector = 'postgres-cdc', + hostname = '${PGHOST:localhost}', + port = '${PGPORT:5432}', + username = '${PGUSER:$USER}', + password = '${PGPASSWORD:}', + database.name = '${PGDATABASE:postgres}', + table.name = 'enum_table', + slot.name = 'enum_to_varchar' +); + +# generated column +statement ok +create table shipments ( + shipment_id INTEGER, + order_id INTEGER, + origin STRING, + destination STRING, + is_arrived boolean, + generated_c INTEGER AS shipment_id + order_id, + PRIMARY KEY (shipment_id) +) with ( + connector = 'postgres-cdc', + hostname = '${PGHOST:localhost}', + port = '${PGPORT:5432}', + username = '${PGUSER:$USER}', + password = '${PGPASSWORD:}', + database.name = '${PGDATABASE:postgres}', + schema.name = 'public', + table.name = 'shipments', + slot.name = 'shipments' +); + +statement ok +drop table shipments; diff --git a/e2e_test/source/cdc/mongodb/mongodb_basic.slt b/e2e_test/source/cdc/mongodb/mongodb_basic.slt index f3a815df0572b..ccbd654002984 100644 --- a/e2e_test/source/cdc/mongodb/mongodb_basic.slt +++ b/e2e_test/source/cdc/mongodb/mongodb_basic.slt @@ -2,7 +2,11 @@ control substitution on statement ok -CREATE TABLE users (_id JSONB PRIMARY KEY, payload JSONB) WITH ( +CREATE TABLE users (_id JSONB PRIMARY KEY, payload JSONB) +INCLUDE TIMESTAMP as commit_ts +INCLUDE DATABASE_NAME as database_name +INCLUDE COLLECTION_NAME as collection_name +WITH ( connector = 'mongodb-cdc', mongodb.url = 'mongodb://mongodb:27017/?replicaSet=rs0', collection.name = 'random_data.*' @@ -24,5 +28,18 @@ select count(*) from normalized_users; ---- 55 +# historical data +query I +select count(*) from users where commit_ts = '1970-01-01 00:00:00+00:00'; +---- +55 + +query TT +select database_name, collection_name FROM users LIMIT 2; +---- +random_data users +random_data users + + statement ok DROP TABLE users cascade diff --git a/e2e_test/source/cdc/mysql_cdc.sql b/e2e_test/source/cdc/mysql_cdc.sql index 0ac47fc7fbd1b..95c7c53ae1ef0 100644 --- a/e2e_test/source/cdc/mysql_cdc.sql +++ b/e2e_test/source/cdc/mysql_cdc.sql @@ -19,15 +19,15 @@ VALUES (default,"scooter","Small 2-wheel scooter"), (default,"hammer","14oz carpenter's hammer"), (default,"hammer","16oz carpenter's hammer"), (default,"rocks","box of assorted rocks"), - (default,"jacket","water resistent black wind breaker"), + (default,"jacket","water resistant black wind breaker"), (default,"spare tire","24 inch spare tire"); CREATE TABLE orders ( order_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, order_date DATETIME NOT NULL, - customer_name VARCHAR(255) NOT NULL, - price DECIMAL(10, 5) NOT NULL, + `cusTomer_Name` VARCHAR(255) NOT NULL, + `priCE` DECIMAL(10, 5) NOT NULL, product_id INTEGER NOT NULL, order_status BOOLEAN NOT NULL -- Whether order has been placed ) AUTO_INCREMENT = 10001; @@ -49,9 +49,11 @@ VALUES (1,1,'no'), (3,3,'no'), (4,4,'no'); +-- This user is for non-shared CDC CREATE USER 'dbz'@'%' IDENTIFIED BY '123456'; GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'dbz'@'%'; +-- This user is for shared CDC CREATE USER 'rwcdc'@'%' IDENTIFIED BY '123456'; GRANT SELECT, RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'rwcdc'@'%'; diff --git a/e2e_test/source/cdc/postgres_cdc.sql b/e2e_test/source/cdc/postgres_cdc.sql index 0f8961f5289d2..43a120a19d50f 100644 --- a/e2e_test/source/cdc/postgres_cdc.sql +++ b/e2e_test/source/cdc/postgres_cdc.sql @@ -1,4 +1,5 @@ -- PG +DROP TABLE IF EXISTS shipments; CREATE TABLE shipments ( shipment_id SERIAL NOT NULL PRIMARY KEY, order_id SERIAL NOT NULL, @@ -31,10 +32,11 @@ INSERT INTO person VALUES (1001, 'peter white', 'myckhsp@xpmpe.com', '1781 2313 INSERT INTO person VALUES (1002, 'sarah spencer', 'wipvdbm@dkaap.com', '3453 4987 9481 6270', 'los angeles'); create schema abs; -create table abs.t1 (v1 int primary key, v2 double precision, v3 varchar, v4 numeric); -create publication my_publicaton for table abs.t1 (v1, v3); +create table abs.t1 ("V1" int primary key, v2 double precision, v3 varchar, v4 numeric); +create publication my_publicaton for table abs.t1 ("V1", v3); insert into abs.t1 values (1, 1.1, 'aaa', '5431.1234'); +CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); CREATE TABLE IF NOT EXISTS postgres_all_types( c_boolean boolean, @@ -52,6 +54,8 @@ CREATE TABLE IF NOT EXISTS postgres_all_types( c_timestamptz timestamptz, c_interval interval, c_jsonb jsonb, + c_uuid uuid, + c_enum mood, c_boolean_array boolean[], c_smallint_array smallint[], c_integer_array integer[], @@ -67,11 +71,14 @@ CREATE TABLE IF NOT EXISTS postgres_all_types( c_timestamptz_array timestamptz[], c_interval_array interval[], c_jsonb_array jsonb[], - c_uuid uuid, + c_uuid_array uuid[], + c_enum_array mood[], PRIMARY KEY (c_boolean,c_bigint,c_date) ); -INSERT INTO postgres_all_types VALUES ( False, 0, 0, 0, 0, 0, 0, '', '\x00', '0001-01-01', '00:00:00', '0001-01-01 00:00:00'::timestamp, '0001-01-01 00:00:00'::timestamptz, interval '0 second', '{}', array[]::boolean[], array[]::smallint[], array[]::integer[], array[]::bigint[], array[]::decimal[], array[]::real[], array[]::double precision[], array[]::varchar[], array[]::bytea[], array[]::date[], array[]::time[], array[]::timestamp[], array[]::timestamptz[], array[]::interval[], array[]::jsonb[], null); -INSERT INTO postgres_all_types VALUES ( False, -32767, -2147483647, -9223372036854775807, -10.0, -9999.999999, -10000.0, '', '\x00', '0001-01-01', '00:00:00', '0001-01-01 00:00:00'::timestamp, '0001-01-01 00:00:00'::timestamptz, interval '0 second', '{}', array[False::boolean]::boolean[], array[-32767::smallint]::smallint[], array[-2147483647::integer]::integer[], array[-9223372036854775807::bigint]::bigint[], array[-10.0::decimal]::decimal[], array[-9999.999999::real]::real[], array[-10000.0::double precision]::double precision[], array[''::varchar]::varchar[], array['\x00'::bytea]::bytea[], array['0001-01-01'::date]::date[], array['00:00:00'::time]::time[], array['0001-01-01 00:00:00'::timestamp::timestamp]::timestamp[], array['0001-01-01 00:00:00'::timestamptz::timestamptz]::timestamptz[], array[interval '0 second'::interval]::interval[], array['{}'::jsonb]::jsonb[], 'bb488f9b-330d-4012-b849-12adeb49e57e'); +INSERT INTO postgres_all_types VALUES ( False, 0, 0, 0, 0, 0, 0, '', '\x00', '0001-01-01', '00:00:00', '2001-01-01 00:00:00'::timestamp, '2001-01-01 00:00:00-8'::timestamptz, interval '0 second', '{}', null, 'sad', array[]::boolean[], array[]::smallint[], array[]::integer[], array[]::bigint[], array[]::decimal[], array[]::real[], array[]::double precision[], array[]::varchar[], array[]::bytea[], array[]::date[], array[]::time[], array[]::timestamp[], array[]::timestamptz[], array[]::interval[], array[]::jsonb[], array[]::uuid[], array[]::mood[]); +INSERT INTO postgres_all_types VALUES ( False, -32767, -2147483647, -9223372036854775807, -10.0, -9999.999999, -10000.0, 'd', '\x00', '0001-01-01', '00:00:00', '2001-01-01 00:00:00'::timestamp, '2001-01-01 00:00:00-8'::timestamptz, interval '0 second', '{}', 'bb488f9b-330d-4012-b849-12adeb49e57e', 'happy', array[False::boolean]::boolean[], array[-32767::smallint]::smallint[], array[-2147483647::integer]::integer[], array[-9223372036854775807::bigint]::bigint[], array[-10.0::decimal]::decimal[], array[-9999.999999::real]::real[], array[-10000.0::double precision]::double precision[], array[''::varchar]::varchar[], array['\x00'::bytea]::bytea[], array['0001-01-01'::date]::date[], array['00:00:00'::time]::time[], array['2001-01-01 00:00:00'::timestamp::timestamp]::timestamp[], array['2001-01-01 00:00:00-8'::timestamptz::timestamptz]::timestamptz[], array[interval '0 second'::interval]::interval[], array['{}'::jsonb]::jsonb[], '{bb488f9b-330d-4012-b849-12adeb49e57e}', '{happy,ok,NULL}'); +INSERT INTO postgres_all_types VALUES ( False, 1, 123, 1234567890, 123.45, 123.45, 123.456, 'example', '\xDEADBEEF', '0024-01-01', '12:34:56', '2024-05-19 12:34:56', '2024-05-19 12:34:56+00', INTERVAL '1 day', '{"key": "value"}', '123e4567-e89b-12d3-a456-426614174000', 'happy', ARRAY[NULL, TRUE]::boolean[], ARRAY[NULL, 1::smallint], ARRAY[NULL, 123], ARRAY[NULL, 1234567890], ARRAY[NULL, 123.45::numeric], ARRAY[NULL, 123.45::real], ARRAY[NULL, 123.456], ARRAY[NULL, 'example'], ARRAY[NULL, '\xDEADBEEF'::bytea], ARRAY[NULL, '2024-05-19'::date], ARRAY[NULL, '12:34:56'::time], ARRAY[NULL, '2024-05-19 12:34:56'::timestamp], ARRAY[NULL, '2024-05-19 12:34:56+00'::timestamptz], ARRAY[NULL, INTERVAL '1 day'], ARRAY[NULL, '{"key": "value"}'::jsonb], ARRAY[NULL, '123e4567-e89b-12d3-a456-426614174000'::uuid], ARRAY[NULL, 'happy'::mood]); +INSERT INTO postgres_all_types VALUES ( False, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, '0024-05-19', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); create table numeric_table(id int PRIMARY KEY, num numeric); insert into numeric_table values(1, 3.14); @@ -88,3 +95,14 @@ insert into numeric_table values(7, 'Infinity'::numeric); create table numeric_list(id int primary key, num numeric[]); insert into numeric_list values(1, '{3.14, 6, 57896044618658097711785492504343953926634992332820282019728792003956564819967, 57896044618658097711785492504343953926634992332820282019728792003956564819968, 115792089237316195423570985008687907853269984665640564039457584007913129639936.555555}'); insert into numeric_list values(2, '{nan, infinity, -infinity}'); + +CREATE TABLE enum_table ( + id int PRIMARY KEY, + current_mood mood +); +INSERT INTO enum_table VALUES (1, 'happy'); + +CREATE TABLE list_with_null(id int primary key, my_int int[], my_num numeric[], my_num_1 numeric[], my_num_2 numeric[], my_mood mood[], my_uuid uuid[], my_bytea bytea[]); +INSERT INTO list_with_null VALUES (1, '{1,2,NULL}', '{1.1,inf,NULL}', '{1.1,inf,NULL}', '{1.1,inf,NULL}', '{happy,ok,NULL}', '{bb488f9b-330d-4012-b849-12adeb49e57e,bb488f9b-330d-4012-b849-12adeb49e57f, NULL}', '{\\x00,\\x01,NULL}'); +INSERT INTO list_with_null VALUES (2, '{NULL,3,4}', '{2.2,0,NULL}' , '{2.2,0,NULL}', '{2.2,0,NULL}', '{happy,ok,sad}', '{2de296df-eda7-4202-a81f-1036100ef4f6,2977afbc-0b12-459c-a36f-f623fc9e9840}', '{\\x00,\\x01,\\x02}'); +INSERT INTO list_with_null VALUES (5, NULL, NULL, NULL, NULL, NULL, NULL, NULL); diff --git a/e2e_test/source/cdc/postgres_cdc_insert.sql b/e2e_test/source/cdc/postgres_cdc_insert.sql index 2ff71bf389615..4c0d0dee48b42 100644 --- a/e2e_test/source/cdc/postgres_cdc_insert.sql +++ b/e2e_test/source/cdc/postgres_cdc_insert.sql @@ -12,6 +12,11 @@ SELECT pg_current_wal_lsn(); select * from pg_publication_tables where pubname='rw_publication'; select * from public.person order by id; +INSERT INTO postgres_all_types VALUES ( True, 0, 0, 0, 0, 0, 0, '', '\x00', '0001-01-01', '00:00:00', '2001-01-01 00:00:00'::timestamp, '2001-01-01 00:00:00-8'::timestamptz, interval '0 second', '{}', null, 'sad', array[]::boolean[], array[]::smallint[], array[]::integer[], array[]::bigint[], array[]::decimal[], array[]::real[], array[]::double precision[], array[]::varchar[], array[]::bytea[], array[]::date[], array[]::time[], array[]::timestamp[], array[]::timestamptz[], array[]::interval[], array[]::jsonb[], array[]::uuid[], array[]::mood[]); +INSERT INTO postgres_all_types VALUES ( True, -32767, -2147483647, -9223372036854775807, -10.0, -9999.999999, -10000.0, 'd', '\x00', '0001-01-01', '00:00:00', '2001-01-01 00:00:00'::timestamp, '2001-01-01 00:00:00-8'::timestamptz, interval '0 second', '{}', 'bb488f9b-330d-4012-b849-12adeb49e57e', 'happy', array[False::boolean]::boolean[], array[-32767::smallint]::smallint[], array[-2147483647::integer]::integer[], array[-9223372036854775807::bigint]::bigint[], array[-10.0::decimal]::decimal[], array[-9999.999999::real]::real[], array[-10000.0::double precision]::double precision[], array[''::varchar]::varchar[], array['\x00'::bytea]::bytea[], array['0001-01-01'::date]::date[], array['00:00:00'::time]::time[], array['2001-01-01 00:00:00'::timestamp::timestamp]::timestamp[], array['2001-01-01 00:00:00-8'::timestamptz::timestamptz]::timestamptz[], array[interval '0 second'::interval]::interval[], array['{}'::jsonb]::jsonb[], '{bb488f9b-330d-4012-b849-12adeb49e57e}', '{happy,ok,NULL}'); +INSERT INTO postgres_all_types VALUES ( True, 1, 123, 1234567890, 123.45, 123.45, 123.456, 'example', '\xDEADBEEF', '0024-01-01', '12:34:56', '2024-05-19 12:34:56', '2024-05-19 12:34:56+00', INTERVAL '1 day', '{"key": "value"}', '123e4567-e89b-12d3-a456-426614174000', 'happy', ARRAY[NULL, TRUE]::boolean[], ARRAY[NULL, 1::smallint], ARRAY[NULL, 123], ARRAY[NULL, 1234567890], ARRAY[NULL, 123.45::numeric], ARRAY[NULL, 123.45::real], ARRAY[NULL, 123.456], ARRAY[NULL, 'example'], ARRAY[NULL, '\xDEADBEEF'::bytea], ARRAY[NULL, '2024-05-19'::date], ARRAY[NULL, '12:34:56'::time], ARRAY[NULL, '2024-05-19 12:34:56'::timestamp], ARRAY[NULL, '2024-05-19 12:34:56+00'::timestamptz], ARRAY[NULL, INTERVAL '1 day'], ARRAY[NULL, '{"key": "value"}'::jsonb], ARRAY[NULL, '123e4567-e89b-12d3-a456-426614174000'::uuid], ARRAY[NULL, 'happy'::mood]); +INSERT INTO postgres_all_types VALUES ( True, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, '0024-05-19', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + insert into numeric_table values(102, 57896044618658097711785492504343953926634992332820282019728792003956564819967); --- 2^255 insert into numeric_table values(103, 57896044618658097711785492504343953926634992332820282019728792003956564819968); @@ -20,3 +25,10 @@ insert into numeric_table values(104, 115792089237316195423570985008687907853269 insert into numeric_table values(105, 115792089237316195423570985008687907853269984665640564039457584007913129639936.555555); insert into numeric_table values(106, 'NaN'::numeric); insert into numeric_table values(107, 'Infinity'::numeric); + +INSERT INTO enum_table VALUES (3, 'sad'); +--- to avoid escaping issues of psql -c "", we insert this row here and check the result in check_new_rows.slt +INSERT INTO list_with_null VALUES (3, '{NULL,-3,-4}', '{NULL,nan,-inf}', '{NULL,nan,-inf}', '{NULL,nan,-inf}', '{NULL,sad,ok}', '{NULL,471acecf-a4b4-4ed3-a211-7fb2291f159f,9bc35adf-fb11-4130-944c-e7eadb96b829}', '{NULL,\\x99,\\xAA}'); +INSERT INTO list_with_null VALUES (4, '{-4,-5,-6}', '{NULL,-99999999999999999.9999}', '{NULL,-99999999999999999.9999}', '{NULL,-99999999999999999.9999}', '{NULL,sad,ok}', '{b2e4636d-fa03-4ad4-bf16-029a79dca3e2}', '{\\x88,\\x99,\\xAA}'); +INSERT INTO list_with_null VALUES (6, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + diff --git a/e2e_test/source/cdc_inline/alter/cdc_table_alter.slt b/e2e_test/source/cdc_inline/alter/cdc_table_alter.slt new file mode 100644 index 0000000000000..6bea5dce2fe45 --- /dev/null +++ b/e2e_test/source/cdc_inline/alter/cdc_table_alter.slt @@ -0,0 +1,242 @@ +control substitution on + +# mysql env vars will be read from the `.risingwave/config/risedev-env` file + +system ok +mysql -e " + SET GLOBAL time_zone = '+00:00'; +" + +system ok +mysql -e " + DROP DATABASE IF EXISTS testdb1; + CREATE DATABASE testdb1; + USE testdb1; + CREATE TABLE products ( + id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description VARCHAR(512) + ); + ALTER TABLE products AUTO_INCREMENT = 101; + INSERT INTO products + VALUES (default,'scooter','Small 2-wheel scooter'), + (default,'car battery','12V car battery'), + (default,'12-pack drill','12-pack of drill bits with sizes ranging from #40 to #3'), + (default,'hammer','12oz carpenter s hammer'), + (default,'hammer','14oz carpenter s hammer'), + (default,'hammer','16oz carpenter s hammer'), + (default,'rocks','box of assorted rocks'), + (default,'jacket','water resistent black wind breaker'), + (default,'spare tire','24 inch spare tire'); + CREATE TABLE orders ( + order_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, + order_date DATETIME NOT NULL, + customer_name VARCHAR(255) NOT NULL, + price DECIMAL(10, 5) NOT NULL, + product_id INTEGER NOT NULL, + order_status BOOLEAN NOT NULL + ) AUTO_INCREMENT = 10001; + INSERT INTO orders + VALUES (default, '2020-07-30 10:08:22', 'Jark', 50.50, 102, false), + (default, '2020-07-30 10:11:09', 'Sally', 15.00, 105, false), + (default, '2020-07-30 12:00:30', 'Edward', 25.25, 106, false); +" + +statement ok +create source mysql_source with ( + connector = 'mysql-cdc', + hostname = '${MYSQL_HOST}', + port = '${MYSQL_TCP_PORT}', + username = 'root', + password = '${MYSQL_PWD}', + database.name = 'testdb1', + server.id = '5185' +); + +statement ok +create table my_products ( id INT, + name STRING, + description STRING, + PRIMARY KEY (id) +) from mysql_source table 'testdb1.products'; + +statement ok +create table my_orders ( + order_id int, + order_date timestamp, + customer_name string, + price decimal, + product_id int, + order_status smallint, + PRIMARY KEY (order_id) +) from mysql_source table 'testdb1.orders'; + +system ok +psql -c " + DROP TABLE IF EXISTS shipments1; + CREATE TABLE shipments1 ( + shipment_id SERIAL NOT NULL PRIMARY KEY, + order_id SERIAL NOT NULL, + origin VARCHAR(255) NOT NULL, + destination VARCHAR(255) NOT NULL, + is_arrived BOOLEAN NOT NULL + ); + ALTER SEQUENCE public.shipments1_shipment_id_seq RESTART WITH 1001; + INSERT INTO shipments1 + VALUES (default,10001,'Beijing','Shanghai',false), + (default,10002,'Hangzhou','Shanghai',false), + (default,10003,'Shanghai','Hangzhou',false); +" + +statement ok +create source pg_source with ( + connector = 'postgres-cdc', + hostname = '${PGHOST:localhost}', + port = '${PGPORT:5432}', + username = '${PGUSER:$USER}', + password = '${PGPASSWORD:}', + database.name = '${PGDATABASE:postgres}', + slot.name = 'cdc_alter_test' +); + +statement ok +create table pg_shipments ( + shipment_id INTEGER, + order_id INTEGER, + origin STRING, + destination STRING, + is_arrived boolean, + PRIMARY KEY (shipment_id) +) from pg_source table 'public.shipments1'; + +# Create a mview join orders, products and shipments +statement ok +create materialized view enriched_orders as SELECT o.*, p.name, p.description, s.shipment_id, s.origin, s.destination, s.is_arrived + FROM my_orders AS o + LEFT JOIN my_products AS p ON o.product_id = p.id + LEFT JOIN pg_shipments AS s ON o.order_id = s.order_id; + + +sleep 3s + +query III +select order_id, product_id, shipment_id from enriched_orders order by order_id; +---- +10001 102 1001 +10002 105 1002 +10003 106 1003 + + +# alter mysql tables +system ok +mysql -e " + USE testdb1; + ALTER TABLE products ADD COLUMN weight DECIMAL(10, 2) NOT NULL DEFAULT 0.0; + ALTER TABLE orders ADD COLUMN order_comment VARCHAR(255); +" + +# alter cdc tables +statement ok +ALTER TABLE my_products ADD COLUMN weight DECIMAL; + +statement ok +ALTER TABLE my_orders ADD COLUMN order_comment VARCHAR; + +# wait alter ddl +sleep 3s + +query ITTT +SELECT id,name,description,weight FROM my_products order by id limit 3 +---- +101 scooter Small 2-wheel scooter NULL +102 car battery 12V car battery NULL +103 12-pack drill 12-pack of drill bits with sizes ranging from #40 to #3 NULL + + +# update mysql tables +system ok +mysql -e " + USE testdb1; + UPDATE products SET weight = 10.5 WHERE id = 101; + UPDATE products SET weight = 12.5 WHERE id = 102; + UPDATE orders SET order_comment = 'very good' WHERE order_id = 10001; +" + +sleep 3s + +query ITTT +SELECT id,name,description,weight FROM my_products order by id limit 3 +---- +101 scooter Small 2-wheel scooter 10.50 +102 car battery 12V car battery 12.50 +103 12-pack drill 12-pack of drill bits with sizes ranging from #40 to #3 NULL + +query ITTT +SELECT order_id,order_date,customer_name,product_id,order_status,order_comment FROM my_orders order by order_id limit 2 +---- +10001 2020-07-30 10:08:22 Jark 102 0 very good +10002 2020-07-30 10:11:09 Sally 105 0 NULL + + +# alter mysql tables +system ok +mysql -e " + USE testdb1; + ALTER TABLE products DROP COLUMN weight; +" + +# alter cdc table to drop column +statement ok +ALTER TABLE my_products DROP COLUMN weight; + +# wait alter ddl +sleep 3s + +query TTTT +describe my_products; +---- +id integer false NULL +name character varying false NULL +description character varying false NULL +primary key id NULL NULL +distribution key id NULL NULL +table description my_products NULL NULL + + +# alter pg table +system ok +psql -c " + ALTER TABLE shipments1 DROP COLUMN destination; +" + +statement error unable to drop the column due to being referenced by downstream materialized views or sinks +ALTER TABLE pg_shipments DROP COLUMN destination; + +# wait alter ddl +sleep 3s + +# query mv again +query III +select order_id, product_id, shipment_id from enriched_orders order by order_id; +---- +10001 102 1001 +10002 105 1002 +10003 106 1003 + +statement ok +drop materialized view enriched_orders; + +statement ok +drop table my_orders; + +statement ok +create table orders_test (*) from mysql_source table 'testdb1.orders'; + +statement error Not supported: alter a table with empty column definitions +ALTER TABLE orders_test ADD COLUMN order_comment VARCHAR; + +statement ok +drop source mysql_source cascade; + +statement ok +drop source pg_source cascade; diff --git a/e2e_test/source/cdc_inline/auto_schema_map_mysql.slt b/e2e_test/source/cdc_inline/auto_schema_map_mysql.slt new file mode 100644 index 0000000000000..eaa727b610828 --- /dev/null +++ b/e2e_test/source/cdc_inline/auto_schema_map_mysql.slt @@ -0,0 +1,136 @@ +control substitution on + +# test case need to cover all data types +system ok +mysql --protocol=tcp -u root -e "DROP DATABASE IF EXISTS mytest; CREATE DATABASE mytest;" + +system ok +mysql --protocol=tcp -u root mytest -e " + DROP TABLE IF EXISTS mysql_types_test; + CREATE TABLE customers( + id BIGINT PRIMARY KEY, + modified DATETIME, + custinfo JSON + ); + ALTER TABLE customers ADD INDEX zipsa( (CAST(custinfo->'zipcode' AS UNSIGNED ARRAY)) ); + CREATE TABLE IF NOT EXISTS mysql_types_test( + c_boolean boolean, + c_bit bit, + c_tinyint tinyint, + c_smallint smallint, + c_mediumint mediumint, + c_integer integer, + c_Bigint bigint, + c_decimal decimal, + c_float float, + c_double double, + c_char_255 char(255), + c_varchar_10000 varchar(10000), + c_binary_255 binary(255), + c_varbinary_10000 varbinary(10000), + c_date date, + c_time time, + c_datetime datetime, + c_timestamp timestamp, + c_enum ENUM('happy','sad','ok'), + c_json JSON, + PRIMARY KEY (c_boolean,c_Bigint,c_date) + ); + INSERT INTO mysql_types_test VALUES ( False, 0, null, null, -8388608, -2147483647, 9223372036854775806, -10.0, -9999.999999, -10000.0, 'c', 'd', '', '', '1001-01-01', '-838:59:59.000000', '2000-01-01 00:00:00.000000', null, 'happy', '[1,2]'); + INSERT INTO mysql_types_test VALUES ( True, 1, -128, -32767, -8388608, -2147483647, -9223372036854775807, -10.0, -9999.999999, -10000.0, 'a', 'b', '', '', '1001-01-01', '00:00:00', '1998-01-01 00:00:00.000000', '1970-01-01 00:00:01', 'sad', '[3,4]'); + " + +statement ok +create source mysql_source with ( + connector = 'mysql-cdc', + hostname = '${MYSQL_HOST:localhost}', + port = '${MYSQL_TCP_PORT:8306}', + username = 'root', + password = '${MYSQL_PWD:}', + database.name = 'mytest', + server.id = '5601' +); + +statement ok +create table rw_customers (*) from mysql_source table 'mytest.customers'; + +# Name, Type, Is Hidden, Description +query TTTT +describe rw_customers; +---- +id bigint false NULL +modified timestamp without time zone false NULL +custinfo jsonb false NULL +primary key id NULL NULL +distribution key id NULL NULL +table description rw_customers NULL NULL + +statement ok +create table rw_mysql_types_test (*) from mysql_source table 'mytest.mysql_types_test'; + +sleep 3s + +# Name, Type, Is Hidden, Description +query TTTT +describe rw_mysql_types_test; +---- +c_boolean smallint false NULL +c_bit boolean false NULL +c_tinyint smallint false NULL +c_smallint smallint false NULL +c_mediumint integer false NULL +c_integer integer false NULL +c_bigint bigint false NULL +c_decimal numeric false NULL +c_float real false NULL +c_double double precision false NULL +c_char_255 character varying false NULL +c_varchar_10000 character varying false NULL +c_binary_255 bytea false NULL +c_varbinary_10000 bytea false NULL +c_date date false NULL +c_time time without time zone false NULL +c_datetime timestamp without time zone false NULL +c_timestamp timestamp with time zone false NULL +c_enum character varying false NULL +c_json jsonb false NULL +primary key c_boolean, c_bigint, c_date NULL NULL +distribution key c_boolean, c_bigint, c_date NULL NULL +table description rw_mysql_types_test NULL NULL + +query TTTTTTTTTTTTT +SELECT + c_boolean, + c_bit, + c_tinyint, + c_smallint, + c_mediumint, + c_integer, + c_bigint, + c_decimal, + c_float, + c_double, + c_char_255, + c_varchar_10000, + c_binary_255 +FROM rw_mysql_types_test order by c_boolean; +---- +0 NULL NULL NULL -8388608 -2147483647 9223372036854775806 -10 -10000 -10000 c d \x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +1 NULL -128 -32767 -8388608 -2147483647 -9223372036854775807 -10 -10000 -10000 a b \x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +query TTTTTTTT +SELECT + c_varbinary_10000, + c_date, + c_time, + c_datetime, + c_timestamp, + c_enum, + c_json +FROM rw_mysql_types_test order by c_boolean; +---- +\x 1001-01-01 NULL 2000-01-01 00:00:00 NULL happy [1, 2] +\x 1001-01-01 00:00:00 1998-01-01 00:00:00 1970-01-01 00:00:01+00:00 sad [3, 4] + +statement ok +drop source mysql_source cascade; diff --git a/e2e_test/source/cdc_inline/auto_schema_map_pg.slt b/e2e_test/source/cdc_inline/auto_schema_map_pg.slt new file mode 100644 index 0000000000000..8183a617293b0 --- /dev/null +++ b/e2e_test/source/cdc_inline/auto_schema_map_pg.slt @@ -0,0 +1,168 @@ +control substitution on + +# test case need to cover all data types +system ok +psql -c " + DROP TABLE IF EXISTS postgres_types_test; + CREATE TABLE IF NOT EXISTS postgres_types_test( + c_boolean boolean, + c_smallint smallint, + c_integer integer, + c_bigint bigint, + c_decimal decimal, + c_real real, + c_double_precision double precision, + c_varchar varchar, + c_bytea bytea, + c_date date, + c_time time, + c_timestamp timestamp, + c_timestamptz timestamptz, + c_interval interval, + c_jsonb jsonb, + c_uuid uuid, + c_enum mood, + c_boolean_array boolean[], + c_smallint_array smallint[], + c_integer_array integer[], + c_bigint_array bigint[], + c_decimal_array decimal[], + c_real_array real[], + c_double_precision_array double precision[], + c_varchar_array varchar[], + c_bytea_array bytea[], + c_date_array date[], + c_time_array time[], + c_timestamp_array timestamp[], + c_timestamptz_array timestamptz[], + c_interval_array interval[], + c_jsonb_array jsonb[], + c_uuid_array uuid[], + c_enum_array mood[], + PRIMARY KEY (c_boolean,c_bigint,c_date) + ); + INSERT INTO postgres_types_test VALUES ( False, 0, 0, 0, 0, 0, 0, '', '00'::bytea, '0001-01-01', '00:00:00', '2001-01-01 00:00:00'::timestamp, '2001-01-01 00:00:00-8'::timestamptz, interval '0 second', '{}', null, 'sad', array[]::boolean[], array[]::smallint[], array[]::integer[], array[]::bigint[], array[]::decimal[], array[]::real[], array[]::double precision[], array[]::varchar[], array[]::bytea[], array[]::date[], array[]::time[], array[]::timestamp[], array[]::timestamptz[], array[]::interval[], array[]::jsonb[], array[]::uuid[], array[]::mood[]); + INSERT INTO postgres_types_test VALUES ( False, -32767, -2147483647, -9223372036854775807, -10.0, -9999.999999, -10000.0, 'd', '00'::bytea, '0001-01-01', '00:00:00', '2001-01-01 00:00:00'::timestamp, '2001-01-01 00:00:00-8'::timestamptz, interval '0 second', '{}', 'bb488f9b-330d-4012-b849-12adeb49e57e', 'happy', array[False::boolean]::boolean[], array[-32767::smallint]::smallint[], array[-2147483647::integer]::integer[], array[-9223372036854775807::bigint]::bigint[], array[-10.0::decimal]::decimal[], array[-9999.999999::real]::real[], array[-10000.0::double precision]::double precision[], array[''::varchar]::varchar[], array['00'::bytea]::bytea[], array['0001-01-01'::date]::date[], array['00:00:00'::time]::time[], array['2001-01-01 00:00:00'::timestamp::timestamp]::timestamp[], array['2001-01-01 00:00:00-8'::timestamptz::timestamptz]::timestamptz[], array[interval '0 second'::interval]::interval[], array['{}'::jsonb]::jsonb[], '{bb488f9b-330d-4012-b849-12adeb49e57e}', '{happy,ok,sad}'); + INSERT INTO postgres_types_test VALUES ( False, 1, 123, 1234567890, 123.45, 123.45, 123.456, 'a_varchar', 'DEADBEEF'::bytea, '0024-01-01', '12:34:56', '2024-05-19 12:34:56', '2024-05-19 12:34:56+00', INTERVAL '1 day', to_jsonb('hello'::text), '123e4567-e89b-12d3-a456-426614174000', 'happy', ARRAY[NULL, TRUE]::boolean[], ARRAY[NULL, 1::smallint], ARRAY[NULL, 123], ARRAY[NULL, 1234567890], ARRAY[NULL, 123.45::numeric], ARRAY[NULL, 123.45::real], ARRAY[NULL, 123.456], ARRAY[NULL, 'a_varchar'], ARRAY[NULL, 'DEADBEEF'::bytea], ARRAY[NULL, '2024-05-19'::date], ARRAY[NULL, '12:34:56'::time], ARRAY[NULL, '2024-05-19 12:34:56'::timestamp], ARRAY[NULL, '2024-05-19 12:34:56+00'::timestamptz], ARRAY[NULL, INTERVAL '1 day'], ARRAY[NULL, to_jsonb('hello'::text)], ARRAY[NULL, '123e4567-e89b-12d3-a456-426614174000'::uuid], ARRAY[NULL, 'happy'::mood]); + INSERT INTO postgres_types_test VALUES ( False, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, '0024-05-19', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + " + +statement ok +create source pg_source with ( + connector = 'postgres-cdc', + hostname = '${PGHOST:localhost}', + port = '${PGPORT:5432}', + username = '${PGUSER:$USER}', + password = '${PGPASSWORD:}', + database.name = '${PGDATABASE:postgres}', + slot.name = 'pg_slot' +); + + +statement ok +create table rw_postgres_types_test (*) from pg_source table 'public.postgres_types_test'; + +sleep 3s + +# Name, Type, Is Hidden, Description +query TTTT +describe rw_postgres_types_test; +---- +c_boolean boolean false NULL +c_smallint smallint false NULL +c_integer integer false NULL +c_bigint bigint false NULL +c_decimal numeric false NULL +c_real real false NULL +c_double_precision double precision false NULL +c_varchar character varying false NULL +c_bytea bytea false NULL +c_date date false NULL +c_time time without time zone false NULL +c_timestamp timestamp without time zone false NULL +c_timestamptz timestamp with time zone false NULL +c_interval interval false NULL +c_jsonb jsonb false NULL +c_uuid character varying false NULL +c_enum character varying false NULL +c_boolean_array boolean[] false NULL +c_smallint_array smallint[] false NULL +c_integer_array integer[] false NULL +c_bigint_array bigint[] false NULL +c_decimal_array numeric[] false NULL +c_real_array real[] false NULL +c_double_precision_array double precision[] false NULL +c_varchar_array character varying[] false NULL +c_bytea_array bytea[] false NULL +c_date_array date[] false NULL +c_time_array time without time zone[] false NULL +c_timestamp_array timestamp without time zone[] false NULL +c_timestamptz_array timestamp with time zone[] false NULL +c_interval_array interval[] false NULL +c_jsonb_array jsonb[] false NULL +c_uuid_array character varying[] false NULL +c_enum_array character varying[] false NULL +primary key c_boolean, c_bigint, c_date NULL NULL +distribution key c_boolean, c_bigint, c_date NULL NULL +table description rw_postgres_types_test NULL NULL + +query TTTTTTT +SELECT + c_boolean, + c_smallint, + c_integer, + c_bigint, + c_decimal, + c_real, + c_double_precision, + c_varchar, + c_bytea from rw_postgres_types_test where c_enum = 'happy' order by c_integer; +---- +f -32767 -2147483647 -9223372036854775807 -10.0 -10000 -10000 d \x3030 +f 1 123 1234567890 123.45 123.45 123.456 a_varchar \x4445414442454546 + +query TTTTT +SELECT + c_date, + c_time, + c_timestamp, + c_timestamptz, + c_interval from rw_postgres_types_test where c_enum = 'happy' order by c_integer; +---- +0001-01-01 00:00:00 2001-01-01 00:00:00 2001-01-01 08:00:00+00:00 00:00:00 +0024-01-01 12:34:56 2024-05-19 12:34:56 2024-05-19 12:34:56+00:00 1 day + +query TTTTTTT +SELECT + c_jsonb, + c_uuid, + c_enum, + c_boolean_array, + c_smallint_array, + c_integer_array, + c_bigint_array from rw_postgres_types_test where c_enum = 'happy' order by c_integer; +---- +{} bb488f9b-330d-4012-b849-12adeb49e57e happy {f} {-32767} {-2147483647} {-9223372036854775807} +"hello" 123e4567-e89b-12d3-a456-426614174000 happy {NULL,t} {NULL,1} {NULL,123} {NULL,1234567890} + +query TTTTTTTTTTTTT +SELECT + c_decimal_array, + c_real_array, + c_double_precision_array, + c_varchar_array, + c_bytea_array, + c_date_array, + c_time_array, + c_timestamp_array, + c_timestamptz_array, + c_interval_array, + c_jsonb_array, + c_uuid_array, + c_enum_array from rw_postgres_types_test where c_enum = 'happy' order by c_integer; +---- +{-10.0} {-10000} {-10000} {""} {"\\x3030"} {0001-01-01} {00:00:00} {"2001-01-01 00:00:00"} {"2001-01-01 08:00:00+00:00"} NULL {"{}"} {bb488f9b-330d-4012-b849-12adeb49e57e} {happy,ok,sad} +{NULL,123.45} {NULL,123.45} {NULL,123.456} {NULL,a_varchar} {NULL,"\\x4445414442454546"} {NULL,2024-05-19} {NULL,12:34:56} {NULL,"2024-05-19 12:34:56"} {NULL,"2024-05-19 12:34:56+00:00"} NULL {NULL,"\"hello\""} {NULL,123e4567-e89b-12d3-a456-426614174000} NULL + +statement ok +drop source pg_source cascade; diff --git a/e2e_test/source/cdc_inline/mysql/mysql_create_drop.slt b/e2e_test/source/cdc_inline/mysql/mysql_create_drop.slt deleted file mode 100644 index 59f7a4538ef2c..0000000000000 --- a/e2e_test/source/cdc_inline/mysql/mysql_create_drop.slt +++ /dev/null @@ -1,133 +0,0 @@ -# create and drop CDC mysql tables concurrently - -control substitution on - -statement ok -ALTER SYSTEM SET max_concurrent_creating_streaming_jobs TO 1; - -system ok -mysql --protocol=tcp -u root -e " - DROP DATABASE IF EXISTS testdb1; CREATE DATABASE testdb1; - USE testdb1; - CREATE TABLE tt1 (v1 int primary key, v2 timestamp); - INSERT INTO tt1 VALUES (1, '2023-10-23 10:00:00'); - CREATE TABLE tt2 (v1 int primary key, v2 timestamp); - INSERT INTO tt2 VALUES (2, '2023-10-23 11:00:00'); - CREATE TABLE tt3 (v1 int primary key, v2 timestamp); - INSERT INTO tt3 VALUES (3, '2023-10-23 12:00:00'); - CREATE TABLE tt4 (v1 int primary key, v2 timestamp); - INSERT INTO tt4 VALUES (4, '2023-10-23 13:00:00'); - CREATE TABLE tt5 (v1 int primary key, v2 timestamp); - INSERT INTO tt5 VALUES (5, '2023-10-23 14:00:00');" - -statement ok -create table tt1 (v1 int, - v2 timestamptz, - PRIMARY KEY (v1) -) with ( - connector = 'mysql-cdc', - hostname = '${MYSQL_HOST:localhost}', - port = '${MYSQL_TCP_PORT:8306}', - username = 'dbz', - password = '${MYSQL_PWD:}', - database.name = 'testdb1', - table.name = 'tt1', -); - -statement ok -create table tt2 (v1 int, - v2 timestamptz, - PRIMARY KEY (v1) -) with ( - connector = 'mysql-cdc', - hostname = '${MYSQL_HOST:localhost}', - port = '${MYSQL_TCP_PORT:8306}', - username = 'dbz', - password = '${MYSQL_PWD:}', - database.name = 'testdb1', - table.name = 'tt2', -); - -statement ok -create table tt3 (v1 int, - v2 timestamptz, - PRIMARY KEY (v1) -) with ( - connector = 'mysql-cdc', - hostname = '${MYSQL_HOST:localhost}', - port = '${MYSQL_TCP_PORT:8306}', - username = 'dbz', - password = '${MYSQL_PWD:}', - database.name = 'testdb1', - table.name = 'tt3', -); - -statement ok -create table tt4 (v1 int, - v2 timestamptz, - PRIMARY KEY (v1) -) with ( - connector = 'mysql-cdc', - hostname = '${MYSQL_HOST:localhost}', - port = '${MYSQL_TCP_PORT:8306}', - username = 'dbz', - password = '${MYSQL_PWD:}', - database.name = 'testdb1', - table.name = 'tt4', -); - -statement ok -create table tt5 (v1 int, - v2 timestamptz, - PRIMARY KEY (v1) -) with ( - connector = 'mysql-cdc', - hostname = '${MYSQL_HOST:localhost}', - port = '${MYSQL_TCP_PORT:8306}', - username = 'dbz', - password = '${MYSQL_PWD:}', - database.name = 'testdb1', - table.name = 'tt5', -); - -sleep 5s - -query IT -select * from tt1; ----- -1 2023-10-23 10:00:00+00:00 - -query IT -select * from tt2; ----- -2 2023-10-23 11:00:00+00:00 - -query IT -select * from tt3; ----- -3 2023-10-23 12:00:00+00:00 - -query IT -select * from tt4; ----- -4 2023-10-23 13:00:00+00:00 - -query IT -select * from tt5; ----- -5 2023-10-23 14:00:00+00:00 - -statement ok -drop table tt1; - -statement ok -drop table tt2; - -statement ok -drop table tt3; - -statement ok -drop table tt4; - -statement ok -drop table tt5; diff --git a/e2e_test/source/cdc_inline/postgres_create_drop.slt b/e2e_test/source/cdc_inline/postgres_create_drop.slt index 334f1eb2c9cce..ad7425bab3df9 100644 --- a/e2e_test/source/cdc_inline/postgres_create_drop.slt +++ b/e2e_test/source/cdc_inline/postgres_create_drop.slt @@ -23,6 +23,13 @@ create table tt1 (v1 int, slot.name = 'tt1_slot', ); +sleep 3s + +query IT +SELECT * FROM tt1; +---- +1 2023-10-23 10:00:00+00:00 + statement ok drop table tt1; diff --git a/e2e_test/source/pulsar/astra-streaming.py b/e2e_test/source/pulsar/astra-streaming.py index 299529e170232..b2e653ed5677b 100644 --- a/e2e_test/source/pulsar/astra-streaming.py +++ b/e2e_test/source/pulsar/astra-streaming.py @@ -20,7 +20,7 @@ def do_test(config): cur.execute(f'''CREATE TABLE t (v1 int, v2 varchar) WITH ( connector='pulsar', - topic='persistent://tenant0/default/topic0', + topic='persistent://meetup/default/ci-test', service.url='pulsar+ssl://pulsar-gcp-useast1.streaming.datastax.com:6651', auth.token='{config['ASTRA_STREAMING_TOKEN']}' ) diff --git a/e2e_test/source/pulsar/pulsar.slt b/e2e_test/source/pulsar/pulsar.slt index 2072dfb6f8449..29dc30c60994b 100644 --- a/e2e_test/source/pulsar/pulsar.slt +++ b/e2e_test/source/pulsar/pulsar.slt @@ -1,8 +1,9 @@ query IT nosort select * FROM t; ---- -1 name0 -2 name0 -6 name3 -0 name5 -5 name8 \ No newline at end of file +1 name1 +2 name2 +3 name3 +4 name4 +5 name5 +6 name6 diff --git a/e2e_test/source_inline/README.md b/e2e_test/source_inline/README.md index 3a9070639b8cb..8f766c5637fb6 100644 --- a/e2e_test/source_inline/README.md +++ b/e2e_test/source_inline/README.md @@ -2,7 +2,8 @@ Compared with prior source tests ( `e2e_test/source` ), tests in this directory are expected to be easy to run locally and easy to write. -Refer to https://github.com/risingwavelabs/risingwave/issues/12451#issuecomment-2051861048 for more details. +See the [connector development guide](http://risingwavelabs.github.io/risingwave/connector/intro.html#end-to-end-tests) for more information about how to set up the test environment, +run tests, and write tests. ## Install Dependencies @@ -10,21 +11,3 @@ Some additional tools are needed to run the `system` commands in tests. - `rpk`: Redpanda (Kafka) CLI toolbox. https://docs.redpanda.com/current/get-started/rpk-install/ - `zx`: A tool for writing better scripts. `npm install -g zx` - -## Run tests - -To run locally, use `risedev d` to start services (including external systems like Kafka and Postgres, or specify `user-managed` to use your own service). -Then use `risedev slt` to run the tests, which will load the environment variables (ports, etc.) -according to the services started by `risedev d` . - -```sh -risedev slt 'e2e_test/source_inline/**/*.slt' -``` - -## Write tests - -To write tests, please ensure each file is self-contained and does not depend on running external scripts to setup the environment. - -Use `system` command to setup instead. -For simple cases, you can directly write a bash command; -For more complex cases, you can write a test script (with any language like bash, python, zx), and invoke it in the `system` command. diff --git a/e2e_test/source_inline/cdc/mysql/mysql_create_drop.slt b/e2e_test/source_inline/cdc/mysql/mysql_create_drop.slt new file mode 100644 index 0000000000000..10854d97b6440 --- /dev/null +++ b/e2e_test/source_inline/cdc/mysql/mysql_create_drop.slt @@ -0,0 +1,236 @@ +# create and drop CDC mysql tables concurrently + +control substitution on + +statement ok +ALTER SYSTEM SET max_concurrent_creating_streaming_jobs TO 1; + +system ok +mysql -e " + SET GLOBAL time_zone = '+01:00'; +" + +system ok +mysql -e " + DROP DATABASE IF EXISTS testdb1; CREATE DATABASE testdb1; + USE testdb1; + CREATE TABLE tt1 (v1 int primary key, v2 timestamp); + INSERT INTO tt1 VALUES (1, '2023-10-23 10:00:00'); + CREATE TABLE tt2 (v1 int primary key, v2 timestamp); + INSERT INTO tt2 VALUES (2, '2023-10-23 11:00:00'); + CREATE TABLE tt3 (v1 int primary key, v2 timestamp); + INSERT INTO tt3 VALUES (3, '2023-10-23 12:00:00'); + CREATE TABLE tt4 (v1 int primary key, v2 timestamp); + INSERT INTO tt4 VALUES (4, '2023-10-23 13:00:00'); + CREATE TABLE tt5 (v1 int primary key, v2 timestamp); + INSERT INTO tt5 VALUES (5, '2023-10-23 14:00:00'); +" + +system ok +mysql -e " + DROP USER IF EXISTS 'non-shared-cdc'@'%'; + CREATE USER 'non-shared-cdc'@'%' IDENTIFIED BY '123456'; + GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'non-shared-cdc'@'%'; + # + DROP USER IF EXISTS 'shared-cdc'@'%'; + CREATE USER 'shared-cdc'@'%' IDENTIFIED BY 'abcdef'; + GRANT SELECT, RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'shared-cdc'@'%'; + # + FLUSH PRIVILEGES; +" + +statement ok +create source s with ( + ${RISEDEV_MYSQL_WITH_OPTIONS_COMMON}, + username = 'shared-cdc', + password = 'abcdef', + database.name = 'testdb1', + server.id = '114514' +); + +sleep 2s + +# At the beginning, the source is paused. It will resume after a downstream is created. +system ok +internal_table.mjs --name s --type '' --count +---- +count: 0 + + +statement ok +create table tt1_shared (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) from s table 'testdb1.tt1'; + +sleep 2s + +# The source is resumed. +# SourceExecutor does not handle historical data, and only receives new data after it's created. +# But it can receive offset update at the beginning and periodically +# via the heartbeat message. +system ok +internal_table.mjs --name s --type '' --count +---- +count: 1 + + +statement ok +create table tt1 (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) with ( + ${RISEDEV_MYSQL_WITH_OPTIONS_COMMON}, + username = 'non-shared-cdc', + password = '123456', + database.name = 'testdb1', + table.name = 'tt1', +); + +statement ok +create table tt2 (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) with ( + ${RISEDEV_MYSQL_WITH_OPTIONS_COMMON}, + username = 'non-shared-cdc', + password = '123456', + database.name = 'testdb1', + table.name = 'tt2', +); + +statement ok +create table tt3 (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) with ( + ${RISEDEV_MYSQL_WITH_OPTIONS_COMMON}, + username = 'non-shared-cdc', + password = '123456', + database.name = 'testdb1', + table.name = 'tt3', +); + +statement ok +create table tt4 (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) with ( + ${RISEDEV_MYSQL_WITH_OPTIONS_COMMON}, + username = 'non-shared-cdc', + password = '123456', + database.name = 'testdb1', + table.name = 'tt4', +); + +statement ok +create table tt5 (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) with ( + ${RISEDEV_MYSQL_WITH_OPTIONS_COMMON}, + username = 'non-shared-cdc', + password = '123456', + database.name = 'testdb1', + table.name = 'tt5', +); + +statement ok +create table tt2_shared (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) from s table 'testdb1.tt2'; + +statement ok +create table tt3_shared (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) from s table 'testdb1.tt3'; + +statement ok +create table tt4_shared (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) from s table 'testdb1.tt4'; + +statement ok +create table tt5_shared (v1 int, + v2 timestamptz, + PRIMARY KEY (v1) +) from s table 'testdb1.tt5'; + +sleep 5s + +query IT +select * from tt1; +---- +1 2023-10-23 09:00:00+00:00 + +query IT +select * from tt2; +---- +2 2023-10-23 10:00:00+00:00 + +query IT +select * from tt3; +---- +3 2023-10-23 11:00:00+00:00 + +query IT +select * from tt4; +---- +4 2023-10-23 12:00:00+00:00 + +query IT +select * from tt5; +---- +5 2023-10-23 13:00:00+00:00 + + +query IT +select * from tt1_shared; +---- +1 2023-10-23 09:00:00+00:00 + +query IT +select * from tt2_shared; +---- +2 2023-10-23 10:00:00+00:00 + +query IT +select * from tt3_shared; +---- +3 2023-10-23 11:00:00+00:00 + +query IT +select * from tt4_shared; +---- +4 2023-10-23 12:00:00+00:00 + +query IT +select * from tt5_shared; +---- +5 2023-10-23 13:00:00+00:00 + +statement ok +drop table tt1; + +statement ok +drop table tt2; + +statement ok +drop table tt3; + +statement ok +drop table tt4; + +statement ok +drop table tt5; + +statement ok +drop source s cascade; + +system ok +mysql -e " + SET GLOBAL time_zone = '+00:00'; +" diff --git a/e2e_test/source_inline/commands.toml b/e2e_test/source_inline/commands.toml index 8af865099ac7c..57d09d8237efa 100644 --- a/e2e_test/source_inline/commands.toml +++ b/e2e_test/source_inline/commands.toml @@ -1,9 +1,5 @@ # This file contains commands used by the tests. -[tasks.source-test-hook] -private = true -dependencies = ["check-risedev-env-file"] -env_files = ["${PREFIX_CONFIG}/risedev-env"] # Note about the Kafka CLI tooling: # - Built-in Kafka console tools: @@ -16,19 +12,14 @@ env_files = ["${PREFIX_CONFIG}/risedev-env"] # - rpk: # Golang based. # Style example: RPK_BROKERS=localhost:9092 rpk topic create t -[tasks.kafka-hook] +[tasks.check-kafka] private = true description = "Check if Kafka is started by RiseDev" -dependencies = ["source-test-hook"] +dependencies = ["check-and-load-risedev-env-file"] script = ''' #!/usr/bin/env sh set -e -if [ ! -d "${PREFIX_BIN}/kafka" ]; then - echo "Kafka is not installed in ${PREFIX_BIN}/kafka. Did you enable Kafka using $(tput setaf 4)\`./risedev configure\`$(tput sgr0)?" - exit 1 -fi - if [ -z "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" ]; then echo "RISEDEV_KAFKA_BOOTSTRAP_SERVERS is not set in risedev-env file. Did you start Kafka using $(tput setaf 4)\`./risedev d\`$(tput sgr0)?" exit 1 @@ -38,50 +29,29 @@ fi [tasks.clean-kafka] category = "RiseDev - Test - Source Test - Kafka" description = "Delete all kafka topics." -dependencies = ["kafka-hook"] -command = "rpk" -args = ["topic", "delete", "-r", "*"] - -[tasks.kafka-topics] -category = "RiseDev - Test - Source Test - Kafka" -dependencies = ["kafka-hook"] -script = """ -#!/usr/bin/env sh -set -e -${PREFIX_BIN}/kafka/bin/kafka-topics.sh --bootstrap-server ${RISEDEV_KAFKA_BOOTSTRAP_SERVERS} "$@" -""" - -[tasks.kafka-produce] -category = "RiseDev - Test - Source Test - Kafka" -dependencies = ["kafka-hook"] -script = """ -#!/usr/bin/env sh -set -e -${PREFIX_BIN}/kafka/bin/kafka-console-producer.sh --bootstrap-server ${RISEDEV_KAFKA_BOOTSTRAP_SERVERS} "$@" -""" - -[tasks.kafka-consume] -category = "RiseDev - Test - Source Test - Kafka" -dependencies = ["kafka-hook"] -script = """ +dependencies = ["check-and-load-risedev-env-file"] +script = ''' #!/usr/bin/env sh set -e -${PREFIX_BIN}/kafka/bin/kafka-console-consumer.sh --bootstrap-server ${RISEDEV_KAFKA_BOOTSTRAP_SERVERS} "$@" -""" -[tasks.kafka-consumer-groups] -category = "RiseDev - Test - Source Test - Kafka" -dependencies = ["kafka-hook"] -script = """ -#!/usr/bin/env sh -set -e -${PREFIX_BIN}/kafka/bin/kafka-consumer-groups.sh --bootstrap-server ${RISEDEV_KAFKA_BOOTSTRAP_SERVERS} "$@" -""" +if [ -n "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" ]; then + echo "Deleting all Kafka topics..." + rpk topic delete -r "*" + echo "Deleting all schema registry subjects" + rpk sr subject list | while read -r subject; do + echo "Deleting schema registry subject: $subject" + rpk sr subject delete "$subject" + rpk sr subject delete "$subject" --permanent + done +else + echo "No Kafka to clean." +fi +''' # rpk tools [tasks.rpk] category = "RiseDev - Test - Source Test - Kafka" -dependencies = ["kafka-hook"] +dependencies = ["check-kafka"] # check https://docs.redpanda.com/current/reference/rpk/rpk-x-options/ or rpk -X help/list for options script = """ #!/usr/bin/env sh @@ -98,7 +68,7 @@ rpk "$@" [tasks.redpanda-console] category = "RiseDev - Test - Source Test - Kafka" description = "Start Redpanda console (Kafka GUI) at localhost:8080." -dependencies = ["kafka-hook"] +dependencies = ["check-kafka"] script = ''' #!/usr/bin/env sh set -e diff --git a/e2e_test/source_inline/kafka/add_partition.slt b/e2e_test/source_inline/kafka/add_partition.slt index 9399cf732b973..de1e1fe214551 100644 --- a/e2e_test/source_inline/kafka/add_partition.slt +++ b/e2e_test/source_inline/kafka/add_partition.slt @@ -1,11 +1,11 @@ -# Note: control substitution on will force us to use "\\n" instead of "\n" in commands +# Note: control substitution on will force us to use "\n" instead of "\n" in commands control substitution on system ok rpk topic create test_add_partition -p 3 system ok -cat < { - return (await describe_consumer_group(group_name))["MEMBERS"] + return (await describe_consumer_group(group_name))["MEMBERS"]; }) ); } @@ -71,14 +68,14 @@ async function list_consumer_group_lags(fragment_id) { const groups = await list_consumer_groups(fragment_id); return Promise.all( groups.map(async (group_name) => { - return (await describe_consumer_group(group_name))["TOTAL-LAG"] + return (await describe_consumer_group(group_name))["TOTAL-LAG"]; }) ); } const fragment_id = await get_fragment_id_of_mv(mv); if (command == "list-groups") { - echo`${(await list_consumer_groups(fragment_id))}`; + echo`${await list_consumer_groups(fragment_id)}`; } else if (command == "list-members") { echo`${await list_consumer_group_members(fragment_id)}`; } else if (command == "list-lags") { diff --git a/e2e_test/source_inline/kafka/consumer_group.slt b/e2e_test/source_inline/kafka/consumer_group.slt index a43c6e58b17bf..a71329e46509d 100644 --- a/e2e_test/source_inline/kafka/consumer_group.slt +++ b/e2e_test/source_inline/kafka/consumer_group.slt @@ -1,4 +1,3 @@ -# Note: control substitution on will force us to use "\\n" instead of "\n" in commands control substitution on # Note either `./risedev rpk` or `rpk` is ok here. @@ -43,7 +42,7 @@ c # There are 2 consumer groups, 1 for batch query (not listed below), 1 for MV. # All of them are "Empty" state with 0 members, because we manually `assign` partitions to them. -# At the begginning, the MV's consumer group will not occur. They will be created after committing offset to Kafka. +# At the beginning, the MV's consumer group will not occur. They will be created after committing offset to Kafka. # (enable.auto.commit defaults to true, and auto.commit.interval.ms defaults to 5s) sleep 5s diff --git a/e2e_test/source_inline/kafka/include_key_as.slt b/e2e_test/source_inline/kafka/include_key_as.slt new file mode 100644 index 0000000000000..76125ea2e4dc4 --- /dev/null +++ b/e2e_test/source_inline/kafka/include_key_as.slt @@ -0,0 +1,297 @@ +control substitution on + +system ok +rpk topic delete 'test_include_key' || true; + +system ok +rpk topic create 'test_include_key' + +system ok +cat < ( PRIMARY KEY ([rw_key | ]) ) + INCLUDE KEY [AS ] + WITH (...) + FORMAT UPSERT ENCODE JSON (...) + + +# upsert format must have a pk +statement error +CREATE TABLE upsert_students ( + "ID" INT, + "firstName" VARCHAR, + "lastName" VARCHAR, + age INT, + height REAL, + weight REAL +) +INCLUDE KEY AS rw_key +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'test_include_key') +FORMAT UPSERT ENCODE JSON +---- +db error: ERROR: Failed to run the query + +Caused by: + Protocol error: Primary key must be specified to rw_key + +Hint: For FORMAT UPSERT ENCODE JSON, INCLUDE KEY must be specified and the key column must be used as primary key. +example: + CREATE TABLE ( PRIMARY KEY ([rw_key | ]) ) + INCLUDE KEY [AS ] + WITH (...) + FORMAT UPSERT ENCODE JSON (...) + + +# upsert format pk must be the key column +statement error +CREATE TABLE upsert_students ( + "ID" INT primary key, + "firstName" VARCHAR, + "lastName" VARCHAR, + age INT, + height REAL, + weight REAL +) +INCLUDE KEY AS rw_key +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'test_include_key') +FORMAT UPSERT ENCODE JSON +---- +db error: ERROR: Failed to run the query + +Caused by: + Protocol error: Only "rw_key" can be used as primary key + +Hint: For FORMAT UPSERT ENCODE JSON, INCLUDE KEY must be specified and the key column must be used as primary key. +example: + CREATE TABLE ( PRIMARY KEY ([rw_key | ]) ) + INCLUDE KEY [AS ] + WITH (...) + FORMAT UPSERT ENCODE JSON (...) + + +statement error +CREATE SOURCE upsert_students ( + primary key (rw_key), + "ID" INT, + "firstName" VARCHAR, + "lastName" VARCHAR, + age INT, + height REAL, + weight REAL, +) +INCLUDE KEY AS rw_key +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'test_include_key') +FORMAT UPSERT ENCODE JSON +---- +db error: ERROR: Failed to run the query + +Caused by: + Bind error: can't CREATE SOURCE with FORMAT UPSERT + +Hint: use CREATE TABLE instead + +Hint: For FORMAT UPSERT ENCODE JSON, INCLUDE KEY must be specified and the key column must be used as primary key. +example: + CREATE TABLE ( PRIMARY KEY ([rw_key | ]) ) + INCLUDE KEY [AS ] + WITH (...) + FORMAT UPSERT ENCODE JSON (...) + + +statement ok +CREATE TABLE plain_students ( + "ID" INT, + "firstName" VARCHAR, + "lastName" VARCHAR, + age INT, + height REAL, + weight REAL, +) +INCLUDE KEY AS rw_key +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'test_include_key') +FORMAT PLAIN ENCODE JSON + + +statement ok +CREATE TABLE upsert_students ( + primary key (rw_key), + "ID" INT, + "firstName" VARCHAR, + "lastName" VARCHAR, + age INT, + height REAL, + weight REAL, +) +INCLUDE KEY AS rw_key +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'test_include_key') +FORMAT UPSERT ENCODE JSON + + +system ok +rpk topic delete 'test_additional_columns' || true; + +system ok +rpk topic create 'test_additional_columns' + +system ok +for i in {0..10}; do echo "key$i:{\"a\": $i}" | rpk topic produce test_additional_columns -f "%k:%v\n" -H "header1=v1" -H "header2=v2"; done + +statement error +create table additional_columns (a int) +include key as key_col +include partition as partition_col +include offset as offset_col +include timestamp 'header1' as timestamp_col +include header 'header1' as header_col_1 +include header 'header2' as header_col_2 +include header 'header2' varchar as header_col_3 +include header 'header3' as header_col_4 +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'test_additional_columns') +FORMAT PLAIN ENCODE JSON +---- +db error: ERROR: Failed to run the query + +Caused by: + Protocol error: Only header column can have inner field, but got "timestamp" + + +statement ok +create table additional_columns (a int) +include key as key_col +include partition as partition_col +include offset as offset_col +include timestamp as timestamp_col +include header as header_col_combined +include header 'header1' as header_col_1 +include header 'header2' as header_col_2 +include header 'header2' varchar as header_col_3 +include header 'header3' as header_col_4 +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'test_additional_columns') +FORMAT PLAIN ENCODE JSON + +# Wait enough time to ensure SourceExecutor consumes all Kafka data. +sleep 3s + +query ? +select count(rw_key) from plain_students +---- +15 + +query ??????? +select * from plain_students order by ("ID", "firstName"); +---- +1 Ethan Martinez 18 6.1 180 \x7b224944223a20317d +1 John Doe 18 5.1 150 \x7b224944223a20317d +1 Olivia Hernandez 22 5.6 125 \x7b224944223a20317d +2 Emily Jackson 19 5.4 110 \x7b224944223a20327d +2 Sarah Smith 19 5.5 120 \x7b224944223a20327d +3 Ben Johnson 21 6 175 \x7b224944223a20337d +3 Noah Thompson 21 6.3 195 \x7b224944223a20337d +4 Emma Brown 20 5.3 130 \x7b224944223a20347d +5 Michael Williams 22 6.2 190 \x7b224944223a20357d +6 Leah Davis 18 5.7 140 \x7b224944223a20367d +7 Connor Wilson 19 5.9 160 \x7b224944223a20377d +8 Ava Garcia 21 5.2 115 \x7b224944223a20387d +9 Jacob Anderson 20 5.8 155 \x7b224944223a20397d +NULL NULL NULL NULL NULL NULL \x7b224944223a20377d +NULL NULL NULL NULL NULL NULL \x7b224944223a20387d + + +query ??????? +select * from upsert_students order by "ID"; +---- +1 Ethan Martinez 18 6.1 180 \x7b224944223a20317d +2 Emily Jackson 19 5.4 110 \x7b224944223a20327d +3 Noah Thompson 21 6.3 195 \x7b224944223a20337d +4 Emma Brown 20 5.3 130 \x7b224944223a20347d +5 Michael Williams 22 6.2 190 \x7b224944223a20357d +6 Leah Davis 18 5.7 140 \x7b224944223a20367d +9 Jacob Anderson 20 5.8 155 \x7b224944223a20397d + + +query ? +SELECT count(*) +FROM additional_columns +WHERE key_col IS NOT NULL + AND partition_col IS NOT NULL + AND offset_col IS NOT NULL + AND timestamp_col IS NOT NULL + AND header_col_combined IS NOT NULL +---- +11 + + +query ?? +WITH arr AS (SELECT header_col_combined FROM additional_columns), +unnested AS (SELECT unnest(header_col_combined) FROM arr) +select *, count(*) from unnested group by 1 order by 1; +---- +(header1,"\\x7631") 11 +(header2,"\\x7632") 11 + +query ???? +select header_col_1, header_col_2, header_col_3, header_col_4 from additional_columns limit 1 +---- +\x7631 \x7632 v2 NULL + +statement ok +drop table upsert_students + +statement ok +drop table plain_students + +statement ok +drop table additional_columns diff --git a/e2e_test/source_inline/kafka/protobuf/alter_source.slt b/e2e_test/source_inline/kafka/protobuf/alter_source.slt new file mode 100644 index 0000000000000..e94ffc9541504 --- /dev/null +++ b/e2e_test/source_inline/kafka/protobuf/alter_source.slt @@ -0,0 +1,91 @@ +control substitution on + +system ok +rpk topic delete sr_pb_test || true; \ +(rpk sr subject delete 'sr_pb_test-value' && rpk sr subject delete 'sr_pb_test-value' --permanent) || true; + +system ok +python3 e2e_test/source_inline/kafka/protobuf/pb.py "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" "${RISEDEV_SCHEMA_REGISTRY_URL}" "sr_pb_test" 20 user + +statement ok +CREATE SOURCE src_user +INCLUDE timestamp -- include explicitly here to test a bug found in https://github.com/risingwavelabs/risingwave/pull/17293 +WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'sr_pb_test', + scan.startup.mode = 'earliest' +) +FORMAT PLAIN ENCODE PROTOBUF( + schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}', + message = 'test.User' +); + +statement ok +CREATE MATERIALIZED VIEW mv_user AS SELECT * FROM src_user; + +statement ok +CREATE TABLE t_user WITH ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'sr_pb_test', + scan.startup.mode = 'earliest' +) +FORMAT PLAIN ENCODE PROTOBUF( + schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}', + message = 'test.User' +); + +statement error +SELECT age FROM mv_user; + +statement error +SELECT age FROM t_user; + +# Push more events with extended fields +system ok +python3 e2e_test/source_inline/kafka/protobuf/pb.py "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" "${RISEDEV_SCHEMA_REGISTRY_URL}" "sr_pb_test" 5 user_with_more_fields + +sleep 5s + +# Refresh source schema +statement ok +ALTER SOURCE src_user REFRESH SCHEMA; + +statement ok +CREATE MATERIALIZED VIEW mv_user_more AS SELECT * FROM src_user; + +# Refresh table schema. It consume new data before refresh, so the new fields are NULLs +statement ok +ALTER TABLE t_user REFRESH SCHEMA; + +query ???? +SELECT COUNT(*), MAX(age), MIN(age), SUM(age) FROM mv_user_more; +---- +25 104 0 510 + +query ???? +SELECT COUNT(*), MAX(age), MIN(age), SUM(age) FROM t_user; +---- +25 NULL NULL NULL + +# Push more events with extended fields +system ok +python3 e2e_test/source_inline/kafka/protobuf/pb.py "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" "${RISEDEV_SCHEMA_REGISTRY_URL}" "sr_pb_test" 5 user_with_more_fields + +sleep 5s + +query ???? +SELECT COUNT(*), MAX(age), MIN(age), SUM(age) FROM t_user; +---- +30 104 100 510 + +statement ok +DROP MATERIALIZED VIEW mv_user_more; + +statement ok +DROP TABLE t_user; + +statement ok +DROP MATERIALIZED VIEW mv_user; + +statement ok +DROP SOURCE src_user; diff --git a/e2e_test/source_inline/kafka/protobuf/basic.slt b/e2e_test/source_inline/kafka/protobuf/basic.slt new file mode 100644 index 0000000000000..0eae891d04bcd --- /dev/null +++ b/e2e_test/source_inline/kafka/protobuf/basic.slt @@ -0,0 +1,58 @@ +control substitution on + +system ok +rpk topic delete sr_pb_test || true; \ +(rpk sr subject delete 'sr_pb_test-value' && rpk sr subject delete 'sr_pb_test-value' --permanent) || true; + +system ok +python3 e2e_test/source_inline/kafka/protobuf/pb.py "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" "${RISEDEV_SCHEMA_REGISTRY_URL}" "sr_pb_test" 20 user + +# make sure google/protobuf/source_context.proto is NOT in schema registry +system ok +curl --silent "${RISEDEV_SCHEMA_REGISTRY_URL}/subjects" | grep -v 'google/protobuf/source_context.proto' + +# Create a table. +statement ok +create table sr_pb_test with ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'sr_pb_test', + scan.startup.mode = 'earliest') +FORMAT plain ENCODE protobuf( + schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL}', + message = 'test.User' + ); + +# for multiple schema registry nodes +statement ok +create table sr_pb_test_bk with ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'sr_pb_test', + scan.startup.mode = 'earliest') +FORMAT plain ENCODE protobuf( + schema.registry = '${RISEDEV_SCHEMA_REGISTRY_URL},${RISEDEV_SCHEMA_REGISTRY_URL}', + message = 'test.User' + ); + +# Wait for source +sleep 2s + +# Flush into storage +statement ok +flush; + +query I +select count(*) from sr_pb_test; +---- +20 + +query IT +select min(id), max(id), max((sc).file_name) from sr_pb_test; +---- +0 19 source/context_019.proto + + +statement ok +drop table sr_pb_test; + +statement ok +drop table sr_pb_test_bk; diff --git a/e2e_test/schema_registry/pb.py b/e2e_test/source_inline/kafka/protobuf/pb.py similarity index 73% rename from e2e_test/schema_registry/pb.py rename to e2e_test/source_inline/kafka/protobuf/pb.py index fd6e0dc478b51..4cab50f899e50 100644 --- a/e2e_test/schema_registry/pb.py +++ b/e2e_test/source_inline/kafka/protobuf/pb.py @@ -25,6 +25,7 @@ def get_user(i): sc=SourceContext(file_name="source/context_{:03}.proto".format(i)), ) + def get_user_with_more_fields(i): return user_pb2.User( id=i, @@ -33,15 +34,18 @@ def get_user_with_more_fields(i): city="City_{}".format(i), gender=user_pb2.MALE if i % 2 == 0 else user_pb2.FEMALE, sc=SourceContext(file_name="source/context_{:03}.proto".format(i)), - age=i, + age=100 + i, ) -def send_to_kafka(producer_conf, schema_registry_conf, topic, num_records, get_user_fn, pb_message): + +def send_to_kafka( + producer_conf, schema_registry_conf, topic, num_records, get_user_fn, pb_message +): schema_registry_client = SchemaRegistryClient(schema_registry_conf) serializer = ProtobufSerializer( pb_message, schema_registry_client, - {"use.deprecated.format": False, 'skip.known.types': True}, + {"use.deprecated.format": False, "skip.known.types": True}, ) producer = Producer(producer_conf) @@ -60,7 +64,9 @@ def send_to_kafka(producer_conf, schema_registry_conf, topic, num_records, get_u if __name__ == "__main__": if len(sys.argv) < 6: - print("pb.py ") + print( + "pb.py " + ) exit(1) broker_list = sys.argv[1] @@ -69,20 +75,29 @@ def send_to_kafka(producer_conf, schema_registry_conf, topic, num_records, get_u num_records = int(sys.argv[4]) pb_message = sys.argv[5] - user_pb2 = importlib.import_module(f'protobuf.{pb_message}_pb2') + user_pb2 = importlib.import_module(f"{pb_message}_pb2") all_pb_messages = { - 'user': get_user, - 'user_with_more_fields': get_user_with_more_fields, + "user": get_user, + "user_with_more_fields": get_user_with_more_fields, } - assert pb_message in all_pb_messages, f'pb_message must be one of {list(all_pb_messages.keys())}' + assert ( + pb_message in all_pb_messages + ), f"pb_message must be one of {list(all_pb_messages.keys())}" schema_registry_conf = {"url": schema_registry_url} producer_conf = {"bootstrap.servers": broker_list} try: - send_to_kafka(producer_conf, schema_registry_conf, topic, num_records, all_pb_messages[pb_message], user_pb2.User) + send_to_kafka( + producer_conf, + schema_registry_conf, + topic, + num_records, + all_pb_messages[pb_message], + user_pb2.User, + ) except Exception as e: print("Send Protobuf data to schema registry and kafka failed {}", e) exit(1) diff --git a/e2e_test/schema_registry/protobuf/user.proto b/e2e_test/source_inline/kafka/protobuf/user.proto similarity index 100% rename from e2e_test/schema_registry/protobuf/user.proto rename to e2e_test/source_inline/kafka/protobuf/user.proto diff --git a/e2e_test/schema_registry/protobuf/user_pb2.py b/e2e_test/source_inline/kafka/protobuf/user_pb2.py similarity index 100% rename from e2e_test/schema_registry/protobuf/user_pb2.py rename to e2e_test/source_inline/kafka/protobuf/user_pb2.py diff --git a/e2e_test/schema_registry/protobuf/user_with_more_fields.proto b/e2e_test/source_inline/kafka/protobuf/user_with_more_fields.proto similarity index 100% rename from e2e_test/schema_registry/protobuf/user_with_more_fields.proto rename to e2e_test/source_inline/kafka/protobuf/user_with_more_fields.proto diff --git a/e2e_test/schema_registry/protobuf/user_with_more_fields_pb2.py b/e2e_test/source_inline/kafka/protobuf/user_with_more_fields_pb2.py similarity index 100% rename from e2e_test/schema_registry/protobuf/user_with_more_fields_pb2.py rename to e2e_test/source_inline/kafka/protobuf/user_with_more_fields_pb2.py diff --git a/e2e_test/source_inline/kafka/shared_source.slt b/e2e_test/source_inline/kafka/shared_source.slt new file mode 100644 index 0000000000000..c481e609ffccd --- /dev/null +++ b/e2e_test/source_inline/kafka/shared_source.slt @@ -0,0 +1,214 @@ +control substitution on + +statement ok +SET rw_enable_shared_source TO true; + +system ok +rpk topic create shared_source -p 4 + +system ok +cat << EOF | rpk topic produce shared_source -f "%p %v\n" -p 0 +0 {"v1": 1, "v2": "a"} +1 {"v1": 2, "v2": "b"} +2 {"v1": 3, "v2": "c"} +3 {"v1": 4, "v2": "d"} +EOF + +statement ok +create source s0 (v1 int, v2 varchar) with ( + ${RISEDEV_KAFKA_WITH_OPTIONS_COMMON}, + topic = 'shared_source', + scan.startup.mode = 'earliest' +) FORMAT PLAIN ENCODE JSON; + +query I +select count(*) from rw_internal_tables where name like '%s0%'; +---- +1 + +sleep 1s + +# SourceExecutor's ingestion does not start (state table is empty), even after sleep +system ok +internal_table.mjs --name s0 --type source +---- +(empty) + + +statement ok +create materialized view mv_1 as select * from s0; + +# Wait enough time to ensure SourceExecutor consumes all Kafka data. +sleep 2s + +# SourceExecutor's ingestion started, but it only starts from latest. +system ok +internal_table.mjs --name s0 --type source +---- +(empty) + + +# offset 0 must be backfilled, not from upstream. +system ok +internal_table.mjs --name mv_1 --type sourcebackfill +---- +0,"{""Backfilling"": ""0""}" +1,"{""Backfilling"": ""0""}" +2,"{""Backfilling"": ""0""}" +3,"{""Backfilling"": ""0""}" + + +# This does not affect the behavior for CREATE MATERIALIZED VIEW below. It also uses the shared source, and creates SourceBackfillExecutor. +statement ok +SET rw_enable_shared_source TO false; + +statement ok +create materialized view mv_2 as select * from s0; + +sleep 2s + +query IT rowsort +select v1, v2 from s0; +---- +1 a +2 b +3 c +4 d + +query IT rowsort +select v1, v2 from mv_1; +---- +1 a +2 b +3 c +4 d + +query IT rowsort +select v1, v2 from mv_2; +---- +1 a +2 b +3 c +4 d + +system ok +cat << EOF | rpk topic produce shared_source -f "%p %v\n" -p 0 +0 {"v1": 1, "v2": "aa"} +1 {"v1": 2, "v2": "bb"} +2 {"v1": 3, "v2": "cc"} +3 {"v1": 4, "v2": "dd"} +EOF + +sleep 2s + +# SourceExecutor's finally got new data now. +system ok +internal_table.mjs --name s0 --type source +---- +0,"{""split_info"": {""partition"": 0, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +1,"{""split_info"": {""partition"": 1, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +2,"{""split_info"": {""partition"": 2, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +3,"{""split_info"": {""partition"": 3, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" + + +query IT rowsort +select v1, v2 from s0; +---- +1 a +1 aa +2 b +2 bb +3 c +3 cc +4 d +4 dd + +query IT rowsort +select v1, v2 from mv_1; +---- +1 a +1 aa +2 b +2 bb +3 c +3 cc +4 d +4 dd + + +# start_offset changed to 1 +system ok +internal_table.mjs --name s0 --type source +---- +0,"{""split_info"": {""partition"": 0, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +1,"{""split_info"": {""partition"": 1, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +2,"{""split_info"": {""partition"": 2, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +3,"{""split_info"": {""partition"": 3, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" + + +# The result is non-deterministic: +# If the upstream row comes before the backfill row, it will be ignored, and the result state is "{""Backfilling"": ""1""}". +# If the upstream row comes after the backfill row, the result state is Finished. +# Uncomment below and run manually to see the result. + +# system ok +# internal_table.mjs --name mv_1 --type sourcebackfill +# ---- +# 0,"{""Finished""}" +# 1,"{""Finished""}" +# 2,"{""Finished""}" +# 3,"{""Finished""}" + + +system ok +for i in {0..9}; do +sleep 0.1 +cat < anyhow::Result<()> { + let args: Vec = std::env::args().collect(); + let command = args[1].as_str(); + + let use_emulator = std::env::var("PUBSUB_EMULATOR_HOST").is_ok(); + let use_cloud = std::env::var("GOOGLE_APPLICATION_CREDENTIALS_JSON").is_ok(); + if !use_emulator && !use_cloud { + panic!("either PUBSUB_EMULATOR_HOST or GOOGLE_APPLICATION_CREDENTIALS_JSON must be set"); + } + + let config = ClientConfig::default().with_auth().await?; + let client = Client::new(config).await?; + + let topic = client.topic(TOPIC); + + if command == "create" { + // delete and create "test-topic" + for subscription in topic.subscriptions(None).await? { + subscription.delete(None).await?; + } + + let _ = topic.delete(None).await; + topic.create(Some(Default::default()), None).await?; + for i in 0..SUBSCRIPTION_COUNT { + let _ = client + .create_subscription( + format!("test-subscription-{}", i).as_str(), + TOPIC, + SubscriptionConfig { + retain_acked_messages: false, + ..Default::default() + }, + None, + ) + .await?; + } + } else if command == "publish" { + let publisher = topic.new_publisher(Default::default()); + for i in 0..10 { + let data = format!("{{\"v1\":{i},\"v2\":\"name{i}\"}}"); + let a = publisher + .publish(PubsubMessage { + data: data.to_string().into_bytes(), + ..Default::default() + }) + .await; + a.get().await?; + println!("published {}", data); + } + } else { + panic!("unknown command {command}"); + } + + Ok(()) +} diff --git a/e2e_test/source_inline/pubsub/pubsub.slt b/e2e_test/source_inline/pubsub/pubsub.slt new file mode 100644 index 0000000000000..35c94d07b9621 --- /dev/null +++ b/e2e_test/source_inline/pubsub/pubsub.slt @@ -0,0 +1,155 @@ +control substitution on + +system ok +e2e_test/source_inline/pubsub/prepare-data.rs create + +statement error missing field `pubsub.subscription` +CREATE SOURCE s (v1 int, v2 varchar) WITH ( + connector = 'google_pubsub' +) FORMAT PLAIN ENCODE JSON; + +statement error credentials must be set if not using the pubsub emulator +CREATE SOURCE s (v1 int, v2 varchar) WITH ( + connector = 'google_pubsub', + pubsub.subscription = 'test-subscription-1' +) FORMAT PLAIN ENCODE JSON; + +statement error failed to lookup address information +CREATE TABLE s (v1 int, v2 varchar) WITH ( + connector = 'google_pubsub', + pubsub.subscription = 'test-subscription-1', + pubsub.emulator_host = 'invalid_host:5981' +) FORMAT PLAIN ENCODE JSON; + +statement error subscription test-subscription-not-exist does not exist +CREATE TABLE s (v1 int, v2 varchar) WITH ( + ${RISEDEV_PUBSUB_WITH_OPTIONS_COMMON}, + pubsub.subscription = 'test-subscription-not-exist', +) FORMAT PLAIN ENCODE JSON; + +# fail if both start_offset and start_snapshot are provided +statement error specify at most one of start_offset or start_snapshot +CREATE TABLE s (v1 int, v2 varchar) WITH ( + ${RISEDEV_PUBSUB_WITH_OPTIONS_COMMON}, + pubsub.subscription = 'test-subscription-3', + pubsub.start_offset.nanos = '121212', + pubsub.start_snapshot = 'snapshot-that-doesnt-exist' +) FORMAT PLAIN ENCODE JSON; + +statement ok +CREATE TABLE s1 (v1 int, v2 varchar) WITH ( + ${RISEDEV_PUBSUB_WITH_OPTIONS_COMMON}, + pubsub.subscription = 'test-subscription-1', + pubsub.parallelism = 5 +) FORMAT PLAIN ENCODE JSON; + + +# We want to test Pub/Sub ack messages on checkpoint, so disable checkpointing here. +# Note that DDL & flush will trigger checkpoint. +statement ok +ALTER SYSTEM SET checkpoint_frequency TO 114514; + +# We publish data after the tables are created, because DDL will trigger checkpoint. +system ok +e2e_test/source_inline/pubsub/prepare-data.rs publish + +# default ack timeout is 10s. Let it redeliver once. +sleep 15s + +# visibility mode is checkpoint +query IT rowsort +select count(*) from s1; +---- +0 + +statement ok +set visibility_mode = 'all'; + +query IT rowsort +select v1, v2, count(*) FROM s1 group by v1, v2; +---- +0 name0 2 +1 name1 2 +2 name2 2 +3 name3 2 +4 name4 2 +5 name5 2 +6 name6 2 +7 name7 2 +8 name8 2 +9 name9 2 + + +statement ok +RECOVER; + +sleep 1s + +statement ok +flush; + +# After recovery, uncheckpointed data in RisingWave is lost. +query IT rowsort +select count(*) from s1; +---- +0 + +# Wait for another redelivery +sleep 10s + +query IT rowsort +select v1, v2, count(*) FROM s1 group by v1, v2; +---- +0 name0 1 +1 name1 1 +2 name2 1 +3 name3 1 +4 name4 1 +5 name5 1 +6 name6 1 +7 name7 1 +8 name8 1 +9 name9 1 + +# flush will force a checkpoint (and ack Pub/Sub messages on checkpoint) +statement ok +flush; + +query IT rowsort +select v1, v2, count(*) FROM s1 group by v1, v2; +---- +0 name0 1 +1 name1 1 +2 name2 1 +3 name3 1 +4 name4 1 +5 name5 1 +6 name6 1 +7 name7 1 +8 name8 1 +9 name9 1 + +sleep 15s + +# no redelivery any more +query IT rowsort +select v1, v2, count(*) FROM s1 group by v1, v2; +---- +0 name0 1 +1 name1 1 +2 name2 1 +3 name3 1 +4 name4 1 +5 name5 1 +6 name6 1 +7 name7 1 +8 name8 1 +9 name9 1 + + +statement ok +DROP TABLE s1; + +# Restore to the value in src/config/ci-recovery.toml +statement ok +ALTER SYSTEM SET checkpoint_frequency TO 5; diff --git a/e2e_test/sql_migration/check.slt b/e2e_test/sql_migration/check.slt index b4c97bba50bf1..c79721f1145e8 100644 --- a/e2e_test/sql_migration/check.slt +++ b/e2e_test/sql_migration/check.slt @@ -64,6 +64,14 @@ create materialized view mv3 as select * from mv2; statement ok REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA schema1 FROM user1; +query T +show secrets; +---- +secret_1 + +statement ok +drop secret secret_1; + statement error Permission denied drop source src; diff --git a/e2e_test/sql_migration/prepare.slt b/e2e_test/sql_migration/prepare.slt index f0669a4c6b297..ecb295cf6c534 100644 --- a/e2e_test/sql_migration/prepare.slt +++ b/e2e_test/sql_migration/prepare.slt @@ -66,3 +66,8 @@ statement ok create function int_42() returns int language javascript as $$ return 42; $$; + +statement ok +create secret secret_1 with ( + backend = 'meta' +) as 'demo_secret'; diff --git a/e2e_test/streaming/bug_fixes/issue_11915.slt b/e2e_test/streaming/bug_fixes/issue_11915.slt new file mode 100644 index 0000000000000..2f4f111358afd --- /dev/null +++ b/e2e_test/streaming/bug_fixes/issue_11915.slt @@ -0,0 +1,41 @@ +# https://github.com/risingwavelabs/risingwave/issues/11915 + +statement ok +create table t(x int); + +statement ok +create materialized view mv as select x, generate_series(1, 2, x) from t; + +# x = 0 causes generate_series(1, 2, x) to return an error. +statement ok +insert into t values (0), (1); + +statement ok +flush; + +# Output 0 row when the set-returning function returns error. +query II rowsort +select * from mv; +---- +1 1 +1 2 + +# Delete the error row. +statement ok +delete from t where x = 0; + +statement ok +flush; + +# The result should be the same as before. +query II rowsort +select * from mv; +---- +1 1 +1 2 + +statement ok +drop materialized view mv; + +statement ok +drop table t; diff --git a/e2e_test/streaming/bug_fixes/issue_12474.slt b/e2e_test/streaming/bug_fixes/issue_12474.slt new file mode 100644 index 0000000000000..245ca499a255d --- /dev/null +++ b/e2e_test/streaming/bug_fixes/issue_12474.slt @@ -0,0 +1,26 @@ +# https://github.com/risingwavelabs/risingwave/issues/12474 + +statement ok +create table t(x int[]); + +statement ok +create materialized view mv as select 1 / x[1] as bomb, unnest(x) as unnest from t; + +statement ok +insert into t values (array[0, 1]), (array[1]); + +statement ok +flush; + +query II rowsort +select * from mv; +---- +1 1 +NULL 0 +NULL 1 + +statement ok +drop materialized view mv; + +statement ok +drop table t; diff --git a/e2e_test/streaming/bug_fixes/stack_overflow_17342.slt b/e2e_test/streaming/bug_fixes/stack_overflow_17342.slt new file mode 100644 index 0000000000000..01197a299736f --- /dev/null +++ b/e2e_test/streaming/bug_fixes/stack_overflow_17342.slt @@ -0,0 +1,120 @@ +statement ok +SET streaming_parallelism TO 1; + +statement ok +CREATE TABLE t (v int); + +# This query used to overflow the stack during optimization as it generated a left-deep tree +# of `OR xx IS NOT NULL` expression in the filter after each full outer join. +skipif madsim +statement ok +CREATE MATERIALIZED VIEW mv AS +SELECT + count(*) +FROM + t +FULL OUTER JOIN t t1 USING (v) +FULL OUTER JOIN t t2 USING (v) +FULL OUTER JOIN t t3 USING (v) +FULL OUTER JOIN t t4 USING (v) +FULL OUTER JOIN t t5 USING (v) +FULL OUTER JOIN t t6 USING (v) +FULL OUTER JOIN t t7 USING (v) +FULL OUTER JOIN t t8 USING (v) +FULL OUTER JOIN t t9 USING (v) +FULL OUTER JOIN t t10 USING (v) +FULL OUTER JOIN t t11 USING (v) +FULL OUTER JOIN t t12 USING (v) +FULL OUTER JOIN t t13 USING (v) +FULL OUTER JOIN t t14 USING (v) +FULL OUTER JOIN t t15 USING (v) +FULL OUTER JOIN t t16 USING (v) +FULL OUTER JOIN t t17 USING (v) +FULL OUTER JOIN t t18 USING (v) +FULL OUTER JOIN t t19 USING (v) +FULL OUTER JOIN t t20 USING (v) +FULL OUTER JOIN t t21 USING (v) +FULL OUTER JOIN t t22 USING (v) +FULL OUTER JOIN t t23 USING (v) +FULL OUTER JOIN t t24 USING (v) +FULL OUTER JOIN t t25 USING (v) +FULL OUTER JOIN t t26 USING (v) +FULL OUTER JOIN t t27 USING (v) +FULL OUTER JOIN t t28 USING (v) +FULL OUTER JOIN t t29 USING (v) +FULL OUTER JOIN t t30 USING (v) +FULL OUTER JOIN t t31 USING (v) +FULL OUTER JOIN t t32 USING (v) +FULL OUTER JOIN t t33 USING (v) +FULL OUTER JOIN t t34 USING (v) +FULL OUTER JOIN t t35 USING (v) +FULL OUTER JOIN t t36 USING (v) +FULL OUTER JOIN t t37 USING (v) +FULL OUTER JOIN t t38 USING (v) +FULL OUTER JOIN t t39 USING (v) +FULL OUTER JOIN t t40 USING (v) +FULL OUTER JOIN t t41 USING (v) +FULL OUTER JOIN t t42 USING (v) +FULL OUTER JOIN t t43 USING (v) +FULL OUTER JOIN t t44 USING (v) +FULL OUTER JOIN t t45 USING (v) +FULL OUTER JOIN t t46 USING (v) +FULL OUTER JOIN t t47 USING (v) +FULL OUTER JOIN t t48 USING (v) +FULL OUTER JOIN t t49 USING (v) +FULL OUTER JOIN t t50 USING (v) +FULL OUTER JOIN t t51 USING (v) +FULL OUTER JOIN t t52 USING (v) +FULL OUTER JOIN t t53 USING (v) +FULL OUTER JOIN t t54 USING (v) +FULL OUTER JOIN t t55 USING (v) +FULL OUTER JOIN t t56 USING (v) +FULL OUTER JOIN t t57 USING (v) +FULL OUTER JOIN t t58 USING (v) +FULL OUTER JOIN t t59 USING (v) +FULL OUTER JOIN t t60 USING (v) +FULL OUTER JOIN t t61 USING (v) +FULL OUTER JOIN t t62 USING (v) +FULL OUTER JOIN t t63 USING (v) +FULL OUTER JOIN t t64 USING (v) +FULL OUTER JOIN t t65 USING (v) +FULL OUTER JOIN t t66 USING (v) +FULL OUTER JOIN t t67 USING (v) +FULL OUTER JOIN t t68 USING (v) +FULL OUTER JOIN t t69 USING (v) +FULL OUTER JOIN t t70 USING (v) +FULL OUTER JOIN t t71 USING (v) +FULL OUTER JOIN t t72 USING (v) +FULL OUTER JOIN t t73 USING (v) +FULL OUTER JOIN t t74 USING (v) +FULL OUTER JOIN t t75 USING (v) +FULL OUTER JOIN t t76 USING (v) +FULL OUTER JOIN t t77 USING (v) +FULL OUTER JOIN t t78 USING (v) +FULL OUTER JOIN t t79 USING (v) +FULL OUTER JOIN t t80 USING (v) +FULL OUTER JOIN t t81 USING (v) +FULL OUTER JOIN t t82 USING (v) +FULL OUTER JOIN t t83 USING (v) +FULL OUTER JOIN t t84 USING (v) +FULL OUTER JOIN t t85 USING (v) +FULL OUTER JOIN t t86 USING (v) +FULL OUTER JOIN t t87 USING (v) +FULL OUTER JOIN t t88 USING (v) +FULL OUTER JOIN t t89 USING (v) +FULL OUTER JOIN t t90 USING (v) +FULL OUTER JOIN t t91 USING (v) +FULL OUTER JOIN t t92 USING (v) +FULL OUTER JOIN t t93 USING (v) +FULL OUTER JOIN t t94 USING (v) +FULL OUTER JOIN t t95 USING (v) +FULL OUTER JOIN t t96 USING (v) +FULL OUTER JOIN t t97 USING (v) +FULL OUTER JOIN t t98 USING (v) +; + +statement ok +DROP TABLE t CASCADE; + +statement ok +SET streaming_parallelism TO DEFAULT; diff --git a/e2e_test/streaming/changelog.slt b/e2e_test/streaming/changelog.slt new file mode 100644 index 0000000000000..649478ffcb981 --- /dev/null +++ b/e2e_test/streaming/changelog.slt @@ -0,0 +1,178 @@ +statement ok +SET RW_IMPLICIT_FLUSH TO true; + +statement ok +create table t1 (v1 int, v2 int); + +statement ok +create table t2 (v1 int, v2 int); + +statement ok +create table t3 (v1 int primary key, v2 int); + +statement ok +create materialized view mv1 as with sub as changelog from t1 select * from sub; + +statement ok +create materialized view mv2 as with sub as changelog from t2 select * from sub; + +statement ok +create materialized view mv3 as with sub as changelog from t1 select v1, v2 from sub; + +statement ok +create materialized view mv4 as with sub1 as changelog from t1, sub2 as changelog from t2 +select sub1.v1 as v11, sub1.v2 as v12, sub2.v1 as v21, sub2.v2 as v22 from sub1 inner join sub2 on sub1.v1 = sub2.v1; + +statement ok +create materialized view mv5 as with sub1 as changelog from t1, sub2 as changelog from t2 +select sub1.v1 as v11, sub1.v2 as v12, sub2.v1 as v21, sub2.v2 as v22, sub1.changelog_op as op1, sub2.changelog_op as op2 from sub1 inner join sub2 on sub1.v1 = sub2.v1; + +statement ok +create materialized view mv6 as with sub as changelog from t3 select * from sub; + +statement ok +create materialized view mv7(col1,col2,col3) as with sub as changelog from t3 select * from sub; + +statement ok +create materialized view mv8 as with sub as changelog from t2 select *, _changelog_row_id as row_id from sub; + +statement ok +insert into t1 values(1,1),(2,2); + +statement ok +insert into t2 values(1,10),(2,20); + +statement ok +insert into t3 values(5,5),(6,6); + +statement ok +update t1 set v2 = 100 where v1 = 1; + +statement ok +update t2 set v2 = 100 where v1 = 1; + +statement ok +update t3 set v2 = 500 where v1 = 5; + +statement ok +delete from t1 where v1 = 2; + +statement ok +alter materialized view mv7 rename to mv7_rename; + +statement ok +alter table t3 rename to t3_rename; + +query III rowsort +select * from mv1 order by v1; +---- +1 1 1 +1 1 4 +1 100 3 +2 2 1 +2 2 2 + +query III rowsort +select * from mv2 order by v1; +---- +1 10 1 +1 10 4 +1 100 3 +2 20 1 + +query III rowsort +select * from mv3 order by v1; +---- +1 1 +1 1 +1 100 +2 2 +2 2 + +query III rowsort +select * from mv4 order by v11,v21; +---- +1 1 1 10 +1 1 1 10 +1 1 1 10 +1 1 1 10 +1 1 1 100 +1 1 1 100 +1 100 1 10 +1 100 1 10 +1 100 1 100 +2 2 2 20 +2 2 2 20 + + +query III rowsort +select * from mv5 order by v11,v21; +---- +1 1 1 10 1 1 +1 1 1 10 1 4 +1 1 1 10 4 1 +1 1 1 10 4 4 +1 1 1 100 1 3 +1 1 1 100 4 3 +1 100 1 10 3 1 +1 100 1 10 3 4 +1 100 1 100 3 3 +2 2 2 20 1 1 +2 2 2 20 2 1 + +query III rowsort +select * from mv6 order by v1; +---- +5 5 1 +5 5 4 +5 500 3 +6 6 1 + +query III rowsort +select * from mv7_rename order by col1; +---- +5 5 1 +5 5 4 +5 500 3 +6 6 1 + +query III rowsort +select v1 from mv8 order by v1; +---- +1 +1 +1 +2 + +statement ok +drop materialized view mv8; + +statement ok +drop materialized view mv7_rename; + +statement ok +drop materialized view mv6; + +statement ok +drop materialized view mv5; + +statement ok +drop materialized view mv4; + +statement ok +drop materialized view mv3; + +statement ok +drop materialized view mv2; + +statement ok +drop materialized view mv1; + +statement ok +drop table t3_rename; + +statement ok +drop table t2; + +statement ok +drop table t1; \ No newline at end of file diff --git a/e2e_test/streaming/eowc/eowc_over_window.slt b/e2e_test/streaming/eowc/eowc_over_window.slt index fe2570e93b6df..e762d54743e7f 100644 --- a/e2e_test/streaming/eowc/eowc_over_window.slt +++ b/e2e_test/streaming/eowc/eowc_over_window.slt @@ -42,6 +42,15 @@ select from t emit on window close; +statement ok +create materialized view mv4 as +select + *, + first_value(tm) over (partition by bar order by tm session with gap '10 minutes') as window_start, + last_value(tm) over (partition by bar order by tm session with gap '10 minutes') as window_end +from t +emit on window close; + statement ok insert into t values ('2023-05-06 16:51:00', 1, 100) @@ -71,6 +80,12 @@ select * from mv3 order by tm; 2023-05-06 17:30:00 3 200 1 2023-05-06 17:35:00 5 100 3 +query TiiTT +select * from mv4 order by tm; +---- +2023-05-06 16:51:00 1 100 2023-05-06 16:51:00 2023-05-06 16:56:00 +2023-05-06 16:56:00 8 100 2023-05-06 16:51:00 2023-05-06 16:56:00 + statement ok insert into t values ('2023-05-06 18:10:00', 7, 100) @@ -100,6 +115,14 @@ select * from mv3 order by tm; 2023-05-06 17:59:00 4 100 4 2023-05-06 18:01:00 6 200 2 +query TiiTT +select * from mv4 order by tm; +---- +2023-05-06 16:51:00 1 100 2023-05-06 16:51:00 2023-05-06 16:56:00 +2023-05-06 16:56:00 8 100 2023-05-06 16:51:00 2023-05-06 16:56:00 +2023-05-06 17:30:00 3 200 2023-05-06 17:30:00 2023-05-06 17:30:00 +2023-05-06 17:35:00 5 100 2023-05-06 17:35:00 2023-05-06 17:35:00 + statement ok drop materialized view mv1; @@ -109,5 +132,8 @@ drop materialized view mv2; statement ok drop materialized view mv3; +statement ok +drop materialized view mv4; + statement ok drop table t; diff --git a/e2e_test/streaming/now.slt b/e2e_test/streaming/now.slt new file mode 100644 index 0000000000000..9649489fbbd95 --- /dev/null +++ b/e2e_test/streaming/now.slt @@ -0,0 +1,31 @@ +control substitution on + +statement ok +create materialized view mv as +select * from generate_series( + -- $__NOW__ is in nanoseconds, `to_timestamp` expects seconds + to_timestamp($__NOW__ / 1000.0 / 1000.0 / 1000.0) - interval '10 second', + now(), + interval '1 second' +); + +statement ok +flush; + +query I +select count(*) >= 10 from mv; +---- +t + +sleep 2s + +statement ok +flush; + +query I +select count(*) >= 12 from mv; +---- +t + +statement ok +drop materialized view mv; diff --git a/e2e_test/streaming/rate_limit/alter_rate_limit.slt.ignore b/e2e_test/streaming/rate_limit/alter_rate_limit.slt.ignore new file mode 100644 index 0000000000000..7afe279a80f1e --- /dev/null +++ b/e2e_test/streaming/rate_limit/alter_rate_limit.slt.ignore @@ -0,0 +1,35 @@ +-- This test is ignored until alter mv rate limit is fully supported. + +statement ok +CREATE TABLE t (v1 int); + +statement ok +INSERT INTO t VALUES (1); + +statement ok +flush; + +statement ok +SET BACKGROUND_DDL=true; + +statement ok +CREATE MATERIALIZED VIEW streaming_rate_limit_0 with ( streaming_rate_limit = 0 ) AS SELECT * FROM t; + +skipif in-memory +sleep 1s + +query I +select progress from rw_ddl_progress; +---- +0.00% + +statement ok +ALTER MATERIALIZED VIEW streaming_rate_limit_0 SET STREAMING_RATE_LIMIT = 1; + +statement ok +wait; + +query I +select * from streaming_rate_limit_0; +---- +1 \ No newline at end of file diff --git a/e2e_test/subscription/create_table_and_subscription.slt b/e2e_test/subscription/create_table_and_subscription.slt index 94039f98b11cc..fd43567bc52de 100644 --- a/e2e_test/subscription/create_table_and_subscription.slt +++ b/e2e_test/subscription/create_table_and_subscription.slt @@ -4,5 +4,8 @@ create table t1 (v1 int, v2 int); statement ok insert into t1 values (1,2); +statement ok +flush; + statement ok create subscription sub from t1 with(retention = '1D'); \ No newline at end of file diff --git a/e2e_test/subscription/main.py b/e2e_test/subscription/main.py index f8e78813801f2..3ffaefd02cee6 100644 --- a/e2e_test/subscription/main.py +++ b/e2e_test/subscription/main.py @@ -33,15 +33,15 @@ def execute_insert(sql,conn): conn.commit() cur.close() -def check_rows_data(expect_vec,rows,status): - row = rows[0] +def check_rows_data(expect_vec,row,status): + value_len = len(row) for index, value in enumerate(row): - if index == 0: + if index == value_len - 1: continue - if index == 1: + if index == value_len - 2: assert value == status,f"expect {value} but got {status}" continue - assert value == expect_vec[index-2],f"expect {expect_vec[index-2]} but got {value}" + assert value == expect_vec[index],f"expect {expect_vec[index]} but got {value}" def test_cursor_snapshot(): print(f"test_cursor_snapshot") @@ -55,7 +55,7 @@ def test_cursor_snapshot(): execute_insert("declare cur subscription cursor for sub",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([1,2],row,1) + check_rows_data([1,2],row[0],1) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("close cur",conn) @@ -74,7 +74,7 @@ def test_cursor_snapshot_log_store(): execute_insert("declare cur subscription cursor for sub",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([1,2],row,1) + check_rows_data([1,2],row[0],1) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("insert into t1 values(4,4)",conn) @@ -82,9 +82,9 @@ def test_cursor_snapshot_log_store(): execute_insert("insert into t1 values(5,5)",conn) execute_insert("flush",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([4,4],row,1) + check_rows_data([4,4],row[0],1) row = execute_query("fetch next from cur",conn) - check_rows_data([5,5],row,1) + check_rows_data([5,5],row[0],1) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("close cur",conn) @@ -108,11 +108,11 @@ def test_cursor_since_begin(): execute_insert("insert into t1 values(6,6)",conn) execute_insert("flush",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([4,4],row,1) + check_rows_data([4,4],row[0],1) row = execute_query("fetch next from cur",conn) - check_rows_data([5,5],row,1) + check_rows_data([5,5],row[0],1) row = execute_query("fetch next from cur",conn) - check_rows_data([6,6],row,1) + check_rows_data([6,6],row[0],1) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("close cur",conn) @@ -137,7 +137,7 @@ def test_cursor_since_now(): execute_insert("insert into t1 values(6,6)",conn) execute_insert("flush",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([6,6],row,1) + check_rows_data([6,6],row[0],1) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("close cur",conn) @@ -161,26 +161,29 @@ def test_cursor_since_rw_timestamp(): execute_insert("insert into t1 values(6,6)",conn) execute_insert("flush",conn) row = execute_query("fetch next from cur",conn) - rw_timestamp_1 = row[0][0] - check_rows_data([4,4],row,1) + valuelen = len(row[0]) + rw_timestamp_1 = row[0][valuelen - 1] + check_rows_data([4,4],row[0],1) row = execute_query("fetch next from cur",conn) - rw_timestamp_2 = row[0][0] - 1 - check_rows_data([5,5],row,1) + valuelen = len(row[0]) + rw_timestamp_2 = row[0][valuelen - 1] - 1 + check_rows_data([5,5],row[0],1) row = execute_query("fetch next from cur",conn) - rw_timestamp_3 = row[0][0] + 1 - check_rows_data([6,6],row,1) + valuelen = len(row[0]) + rw_timestamp_3 = row[0][valuelen - 1] + 1 + check_rows_data([6,6],row[0],1) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("close cur",conn) execute_insert(f"declare cur subscription cursor for sub since {rw_timestamp_1}",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([4,4],row,1) + check_rows_data([4,4],row[0],1) execute_insert("close cur",conn) execute_insert(f"declare cur subscription cursor for sub since {rw_timestamp_2}",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([5,5],row,1) + check_rows_data([5,5],row[0],1) execute_insert("close cur",conn) execute_insert(f"declare cur subscription cursor for sub since {rw_timestamp_3}",conn) @@ -202,7 +205,7 @@ def test_cursor_op(): execute_insert("declare cur subscription cursor for sub",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([1,2],row,1) + check_rows_data([1,2],row[0],1) row = execute_query("fetch next from cur",conn) assert row == [] @@ -211,24 +214,96 @@ def test_cursor_op(): execute_insert("update t1 set v2 = 10 where v1 = 4",conn) execute_insert("flush",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([4,4],row,1) + check_rows_data([4,4],row[0],1) row = execute_query("fetch next from cur",conn) - check_rows_data([4,4],row,4) + check_rows_data([4,4],row[0],4) row = execute_query("fetch next from cur",conn) - check_rows_data([4,10],row,3) + check_rows_data([4,10],row[0],3) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("delete from t1 where v1 = 4",conn) execute_insert("flush",conn) row = execute_query("fetch next from cur",conn) - check_rows_data([4,10],row,2) + check_rows_data([4,10],row[0],2) row = execute_query("fetch next from cur",conn) assert row == [] execute_insert("close cur",conn) drop_table_subscription() +def test_cursor_with_table_alter(): + print(f"test_cursor_with_table_alter") + create_table_subscription() + conn = psycopg2.connect( + host="localhost", + port="4566", + user="root", + database="dev" + ) + + execute_insert("declare cur subscription cursor for sub",conn) + execute_insert("alter table t1 add v3 int",conn) + execute_insert("insert into t1 values(4,4,4)",conn) + execute_insert("flush",conn) + row = execute_query("fetch next from cur",conn) + check_rows_data([1,2],row[0],1) + row = execute_query("fetch next from cur",conn) + check_rows_data([4,4,4],row[0],1) + execute_insert("insert into t1 values(5,5,5)",conn) + execute_insert("flush",conn) + row = execute_query("fetch next from cur",conn) + check_rows_data([5,5,5],row[0],1) + execute_insert("alter table t1 drop column v2",conn) + execute_insert("insert into t1 values(6,6)",conn) + execute_insert("flush",conn) + row = execute_query("fetch next from cur",conn) + check_rows_data([6,6],row[0],1) + drop_table_subscription() + +def test_cursor_fetch_n(): + print(f"test_cursor_with_table_alter") + create_table_subscription() + conn = psycopg2.connect( + host="localhost", + port="4566", + user="root", + database="dev" + ) + + execute_insert("declare cur subscription cursor for sub",conn) + execute_insert("insert into t1 values(4,4)",conn) + execute_insert("flush",conn) + execute_insert("insert into t1 values(5,5)",conn) + execute_insert("flush",conn) + execute_insert("insert into t1 values(6,6)",conn) + execute_insert("flush",conn) + execute_insert("insert into t1 values(7,7)",conn) + execute_insert("flush",conn) + execute_insert("insert into t1 values(8,8)",conn) + execute_insert("flush",conn) + execute_insert("insert into t1 values(9,9)",conn) + execute_insert("flush",conn) + execute_insert("insert into t1 values(10,10)",conn) + execute_insert("flush",conn) + execute_insert("update t1 set v2 = 100 where v1 = 10",conn) + execute_insert("flush",conn) + row = execute_query("fetch 6 from cur",conn) + assert len(row) == 6 + check_rows_data([1,2],row[0],1) + check_rows_data([4,4],row[1],1) + check_rows_data([5,5],row[2],1) + check_rows_data([6,6],row[3],1) + check_rows_data([7,7],row[4],1) + check_rows_data([8,8],row[5],1) + row = execute_query("fetch 6 from cur",conn) + assert len(row) == 4 + check_rows_data([9,9],row[0],1) + check_rows_data([10,10],row[1],1) + check_rows_data([10,10],row[2],4) + check_rows_data([10,100],row[3],3) + drop_table_subscription() + if __name__ == "__main__": test_cursor_snapshot() test_cursor_op() @@ -236,3 +311,5 @@ def test_cursor_op(): test_cursor_since_rw_timestamp() test_cursor_since_now() test_cursor_since_begin() + test_cursor_with_table_alter() + test_cursor_fetch_n() diff --git a/e2e_test/udf/external_udf.slt b/e2e_test/udf/external_udf.slt index 096a605709d67..7a38506f81563 100644 --- a/e2e_test/udf/external_udf.slt +++ b/e2e_test/udf/external_udf.slt @@ -1,7 +1,7 @@ # Before running this test: # python3 e2e_test/udf/test.py # or: -# cd java/udf-example && mvn package && java -jar target/risingwave-udf-example.jar +# cd e2e_test/udf/java && mvn package && java -jar target/risingwave-udf-example.jar # Create a function. statement ok diff --git a/java/udf-example/README.md b/e2e_test/udf/java/README.md similarity index 100% rename from java/udf-example/README.md rename to e2e_test/udf/java/README.md diff --git a/java/udf-example/pom.xml b/e2e_test/udf/java/pom.xml similarity index 86% rename from java/udf-example/pom.xml rename to e2e_test/udf/java/pom.xml index 8bf51cd108128..7ecd7c54dca17 100644 --- a/java/udf-example/pom.xml +++ b/e2e_test/udf/java/pom.xml @@ -5,17 +5,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - - com.risingwave - risingwave-java-root - 0.1.0-SNAPSHOT - ../pom.xml - - com.risingwave risingwave-udf-example - 0.1.1-SNAPSHOT + 0.1.0-SNAPSHOT udf-example https://docs.risingwave.com/docs/current/udf-java @@ -31,7 +23,7 @@ com.risingwave risingwave-udf - 0.1.3-SNAPSHOT + 0.2.0 com.google.code.gson diff --git a/java/udf-example/src/main/java/com/example/UdfExample.java b/e2e_test/udf/java/src/main/java/com/example/UdfExample.java similarity index 99% rename from java/udf-example/src/main/java/com/example/UdfExample.java rename to e2e_test/udf/java/src/main/java/com/example/UdfExample.java index 883dc5035514c..1702e244bf1ff 100644 --- a/java/udf-example/src/main/java/com/example/UdfExample.java +++ b/e2e_test/udf/java/src/main/java/com/example/UdfExample.java @@ -33,7 +33,7 @@ public class UdfExample { public static void main(String[] args) throws IOException { - try (var server = new UdfServer("0.0.0.0", 8815)) { + try (var server = new UdfServer("localhost", 8815)) { server.addFunction("int_42", new Int42()); server.addFunction("float_to_decimal", new FloatToDecimal()); server.addFunction("sleep", new Sleep()); diff --git a/e2e_test/udf/js_udf.slt b/e2e_test/udf/js_udf.slt index 260fd991f648f..a6cabbd487cde 100644 --- a/e2e_test/udf/js_udf.slt +++ b/e2e_test/udf/js_udf.slt @@ -152,3 +152,86 @@ wave 4 statement ok drop function split; + + +# aggregate function +statement ok +create aggregate weighted_avg(value int, weight int) returns float language javascript as $$ + export function create_state() { + return {sum: 0, weight: 0}; + } + export function accumulate(state, value, weight) { + if (value == null || weight == null) { + return state; + } + state.sum += value * weight; + state.weight += weight; + return state; + } + export function retract(state, value, weight) { + if (value == null || weight == null) { + return state; + } + state.sum -= value * weight; + state.weight -= weight; + return state; + } + export function finish(state) { + if (state.weight == 0) { + return null; + } + return state.sum / state.weight; + } +$$; + +# batch +query F +select weighted_avg(value, weight) from (values (1, 1), (null, 2), (3, 3)) as t(value, weight); +---- +2.5 + +# streaming +statement ok +create table t(value int, weight int); + +statement ok +create materialized view mv as select weighted_avg(value, weight) from t; + +query F +select * from mv; +---- +NULL + +statement ok +insert into t values (1, 1), (null, 2), (3, 3); + +statement ok +flush; + +query F +select * from mv; +---- +2.5 + +statement ok +delete from t where value = 3; + +statement ok +flush; + +query F +select * from mv; +---- +1 + +statement ok +drop materialized view mv; + +statement ok +drop table t; + +statement error "weighted_avg" is an aggregate function +drop function weighted_avg; + +statement ok +drop aggregate weighted_avg; diff --git a/e2e_test/udf/python_udf.slt b/e2e_test/udf/python_udf.slt index b8253c96de4dc..fedb9644160a5 100644 --- a/e2e_test/udf/python_udf.slt +++ b/e2e_test/udf/python_udf.slt @@ -170,6 +170,87 @@ wave 4 statement ok drop function split; + +# aggregate function +statement ok +create aggregate weighted_avg(value int, weight int) returns float language python as $$ +def create_state(): + return (0, 0) +def accumulate(state, value, weight): + if value is None or weight is None: + return state + (s, w) = state + s += value * weight + w += weight + return (s, w) +def retract(state, value, weight): + if value is None or weight is None: + return state + (s, w) = state + s -= value * weight + w -= weight + return (s, w) +def finish(state): + (sum, weight) = state + if weight == 0: + return None + else: + return sum / weight +$$; + +# batch +query F +select weighted_avg(value, weight) from (values (1, 1), (null, 2), (3, 3)) as t(value, weight); +---- +2.5 + +# streaming +statement ok +create table t(value int, weight int); + +statement ok +create materialized view mv as select weighted_avg(value, weight) from t; + +query F +select * from mv; +---- +NULL + +statement ok +insert into t values (1, 1), (null, 2), (3, 3); + +statement ok +flush; + +query F +select * from mv; +---- +2.5 + +statement ok +delete from t where value = 3; + +statement ok +flush; + +query F +select * from mv; +---- +1 + +statement ok +drop materialized view mv; + +statement ok +drop table t; + +statement error "weighted_avg" is an aggregate function +drop function weighted_avg; + +statement ok +drop aggregate weighted_avg; + + statement ok create function mismatched_arguments() returns int language python as $$ def mismatched_arguments(x): diff --git a/e2e_test/udf/requirements.txt b/e2e_test/udf/requirements.txt index 8642e2b1ec254..36688db1ed1ee 100644 --- a/e2e_test/udf/requirements.txt +++ b/e2e_test/udf/requirements.txt @@ -1,2 +1,3 @@ flask -waitress \ No newline at end of file +waitress +arrow_udf==0.2.1 \ No newline at end of file diff --git a/e2e_test/udf/sql_udf.slt b/e2e_test/udf/sql_udf.slt index 866f27abd52ce..5f32bb6f7e024 100644 --- a/e2e_test/udf/sql_udf.slt +++ b/e2e_test/udf/sql_udf.slt @@ -360,7 +360,7 @@ In SQL UDF definition: `select a + b + c + not_be_displayed(c)` ^ -statement error Expected end of statement, found: 💩 +statement error expected end of statement, found: 💩 create function call_regexp_replace() returns varchar language sql as 'select regexp_replace('💩💩💩💩💩foo🤔️bar亲爱的😭baz这不是爱情❤️‍🔥', 'baz(...)', '这是🥵', 'ic')'; # Recursive definition can NOT be accepted at present due to semantic check @@ -401,7 +401,7 @@ statement error return type mismatch detected create function type_mismatch(INT) returns varchar language sql as 'select $1 + 114514 + $1'; # Invalid function body syntax -statement error Expected an expression:, found: EOF at the end +statement error expected an expression:, found: EOF at the end create function add_error(INT, INT) returns int language sql as $$select $1 + $2 +$$; ###################################################################### diff --git a/e2e_test/udf/test.py b/e2e_test/udf/test.py index 6195476a80004..4443a81a6e74d 100644 --- a/e2e_test/udf/test.py +++ b/e2e_test/udf/test.py @@ -19,9 +19,7 @@ from typing import Iterator, List, Optional, Tuple, Any from decimal import Decimal -sys.path.append("src/expr/udf/python") # noqa - -from risingwave.udf import udf, udtf, UdfServer +from arrow_udf import udf, udtf, UdfServer @udf(input_types=[], result_type="INT") @@ -47,13 +45,21 @@ def gcd3(x: int, y: int, z: int) -> int: return gcd(gcd(x, y), z) -@udf(input_types=["BYTEA"], result_type="STRUCT") +@udf( + input_types=["BYTEA"], + result_type="STRUCT", +) def extract_tcp_info(tcp_packet: bytes): src_addr, dst_addr = struct.unpack("!4s4s", tcp_packet[12:20]) src_port, dst_port = struct.unpack("!HH", tcp_packet[20:24]) src_addr = socket.inet_ntoa(src_addr) dst_addr = socket.inet_ntoa(dst_addr) - return src_addr, dst_addr, src_port, dst_port + return { + "src_addr": src_addr, + "dst_addr": dst_addr, + "src_port": src_port, + "dst_port": dst_port, + } @udtf(input_types="INT", result_types="INT") @@ -84,7 +90,7 @@ def hex_to_dec(hex: Optional[str]) -> Optional[Decimal]: return dec -@udf(input_types=["FLOAT8"], result_type="DECIMAL") +@udf(input_types=["FLOAT64"], result_type="DECIMAL") def float_to_decimal(f: float) -> Decimal: return Decimal(f) @@ -120,21 +126,49 @@ def jsonb_array_identity(list: List[Any]) -> List[Any]: return list -@udf(input_types="STRUCT", result_type="STRUCT") +@udf( + input_types="STRUCT", + result_type="STRUCT", +) def jsonb_array_struct_identity(v: Tuple[List[Any], int]) -> Tuple[List[Any], int]: return v -ALL_TYPES = "BOOLEAN,SMALLINT,INT,BIGINT,FLOAT4,FLOAT8,DECIMAL,DATE,TIME,TIMESTAMP,INTERVAL,VARCHAR,BYTEA,JSONB".split( - "," -) + [ - "STRUCT" -] - - @udf( - input_types=ALL_TYPES, - result_type=f"struct<{','.join(ALL_TYPES)}>", + input_types=[ + "boolean", + "int16", + "int32", + "int64", + "float32", + "float64", + "decimal", + "date32", + "time64", + "timestamp", + "interval", + "string", + "binary", + "json", + "struct", + ], + result_type="""struct< + boolean: boolean, + int16: int16, + int32: int32, + int64: int64, + float32: float32, + float64: float64, + decimal: decimal, + date32: date32, + time64: time64, + timestamp: timestamp, + interval: interval, + string: string, + binary: binary, + json: json, + struct: struct, + >""", ) def return_all( bool, @@ -153,28 +187,60 @@ def return_all( jsonb, struct, ): - return ( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, - struct, - ) + return { + "boolean": bool, + "int16": i16, + "int32": i32, + "int64": i64, + "float32": f32, + "float64": f64, + "decimal": decimal, + "date32": date, + "time64": time, + "timestamp": timestamp, + "interval": interval, + "string": varchar, + "binary": bytea, + "json": jsonb, + "struct": struct, + } @udf( - input_types=[t + "[]" for t in ALL_TYPES], - result_type=f"struct<{','.join(t + '[]' for t in ALL_TYPES)}>", + input_types=[ + "boolean[]", + "int16[]", + "int32[]", + "int64[]", + "float32[]", + "float64[]", + "decimal[]", + "date32[]", + "time64[]", + "timestamp[]", + "interval[]", + "string[]", + "binary[]", + "json[]", + "struct[]", + ], + result_type="""struct< + boolean: boolean[], + int16: int16[], + int32: int32[], + int64: int64[], + float32: float32[], + float64: float64[], + decimal: decimal[], + date32: date32[], + time64: time64[], + timestamp: timestamp[], + interval: interval[], + string: string[], + binary: binary[], + json: json[], + struct: struct[], + >""", ) def return_all_arrays( bool, @@ -193,23 +259,23 @@ def return_all_arrays( jsonb, struct, ): - return ( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, - struct, - ) + return { + "boolean": bool, + "int16": i16, + "int32": i32, + "int64": i64, + "float32": f32, + "float64": f64, + "decimal": decimal, + "date32": date, + "time64": time, + "timestamp": timestamp, + "interval": interval, + "string": varchar, + "binary": bytea, + "json": jsonb, + "struct": struct, + } if __name__ == "__main__": diff --git a/e2e_test/udf/wasm/Cargo.toml b/e2e_test/udf/wasm/Cargo.toml index 250bd8132ca53..54c7da45b1af8 100644 --- a/e2e_test/udf/wasm/Cargo.toml +++ b/e2e_test/udf/wasm/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -arrow-udf = "0.2" +arrow-udf = "0.3" genawaiter = "0.99" rust_decimal = "1" serde_json = "1" diff --git a/grafana/README.md b/grafana/README.md index ca3d4da00f9cc..8402421eabb65 100644 --- a/grafana/README.md +++ b/grafana/README.md @@ -1,6 +1,6 @@ # RisingWave Grafana Dashboard -The Grafana dashboard is generated with grafanalib. You'll need +The Grafana dashboard is generated with `grafanalib`. You'll need - Python - grafanalib @@ -29,18 +29,25 @@ And don't forget to include the generated `risingwave--dashboard.json` in t ./update.sh ``` -## Advanced Usage +## Multi-cluster Deployment -We can specify the source uid, dashboard uid, dashboard version, enable namespace filter and enable risingwave_name filter(used in multi-cluster deployment) via env variables. +The `generate.sh` supports multi-cluster deployment. The following environment variables are helpful: -For example, we can use the following query to generate dashboard json used in our benchmark cluster: +- `DASHBOARD_NAMESPACE_FILTER_ENABLED`: When set to `true`, a drop-down list will be added to the Grafana dashboard, and all Prometheus queries will be filtered by the selected namespace. +- `DASHBOARD_RISINGWAVE_NAME_FILTER_ENABLED`: When set to `true`, a drop-down list will be added to the Grafana dashboard, and all Prometheus queries will be filtered by the selected RisingWave name. This is useful when you have multiple RisingWave instances in the same namespace. +- `DASHBOARD_SOURCE_UID`: Set to the UID of your Prometheus source. +- `DASHBOARD_DYNAMIC_SOURCE`: Alternative to `DASHBOARD_SOURCE_UID`. When set to `true`, a drop-down list will be added to the Grafana dashboard to pick any one of the Prometheus sources. +- `DASHBOARD_UID`: Set to the UID of your Grafana dashboard. + +See more details in the `common.py` file. + +Examples: ```bash DASHBOARD_NAMESPACE_FILTER_ENABLED=true \ DASHBOARD_RISINGWAVE_NAME_FILTER_ENABLED=true \ DASHBOARD_SOURCE_UID= \ DASHBOARD_UID= \ -DASHBOARD_VERSION= \ ./generate.sh ``` @@ -51,6 +58,5 @@ DASHBOARD_NAMESPACE_FILTER_ENABLED=true \ DASHBOARD_RISINGWAVE_NAME_FILTER_ENABLED=true \ DASHBOARD_DYNAMIC_SOURCE=true \ DASHBOARD_UID= \ -DASHBOARD_VERSION= \ ./generate.sh ``` diff --git a/grafana/common.py b/grafana/common.py index 9a47bd0c964a9..88a31c1725b62 100644 --- a/grafana/common.py +++ b/grafana/common.py @@ -467,10 +467,9 @@ def timeseries_id(self, title, description, targets): **self.common_options, ) - def table_info(self, title, description, targets, columns): + def table_info(self, title, description, targets, columns, excludeByName=dict.fromkeys(["Time", "Value"], True)): gridPos = self.layout.next_half_width_graph() column_indices = {column: index for index, column in enumerate(columns)} - excludeByName = dict.fromkeys(["Time", "Value"], True) transformations = [ {"id": "organize", "options": {"indexByName": column_indices, "excludeByName": excludeByName}} ] @@ -514,11 +513,15 @@ def table_metric(name, filter=None): def quantile(f, percentiles): quantile_map = { - "60": ["0.6", "60"], + "10": ["0.1", "10"], + "25": ["0.25", "25"], "50": ["0.5", "50"], + "60": ["0.6", "60"], + "75": ["0.75", "75"], "90": ["0.9", "90"], "99": ["0.99", "99"], "999": ["0.999", "999"], + "100": ["1.0", "100"], "max": ["1.0", "max"], } return list( diff --git a/grafana/risingwave-dev-dashboard.dashboard.py b/grafana/risingwave-dev-dashboard.dashboard.py index 34f58d2458764..1d0bab6a73d41 100644 --- a/grafana/risingwave-dev-dashboard.dashboard.py +++ b/grafana/risingwave-dev-dashboard.dashboard.py @@ -40,6 +40,13 @@ def section_actor_info(outer_panels): [panels.table_target(f"group({metric('table_info')}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)")], ["table_id", "table_name", "table_type", "materialized_view_id", "fragment_id", "compaction_group_id"], ), + panels.table_info( + "Actor Count (Group By Compute Node)", + "Actor count per compute node", + [panels.table_target(f"count({metric('actor_info')}) by (compute_node)")], + ["table_id", "table_name", "table_type", "materialized_view_id", "fragment_id", "compaction_group_id"], + dict.fromkeys(["Time"], True) + ) ], ) ] @@ -72,6 +79,17 @@ def section_cluster_node(outer_panels): ) ], ), + panels.timeseries_percentage( + "Node Memory relative", + "Memory usage relative to k8s resource limit of container. Only works in K8s environment", + [ + panels.target( + "(avg by(namespace, pod) (container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\"})) / ( sum by(namespace, pod) (kube_pod_container_resource_limits{namespace=~\"$namespace\", pod=~\"$pod\", container=\"$component\", resource=\"memory\", unit=\"byte\"}))", + "avg memory usage @ {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ) + ], + ), panels.timeseries_cpu( "Node CPU", "The CPU usage of each RisingWave component.", @@ -82,12 +100,23 @@ def section_cluster_node(outer_panels): % (COMPONENT_LABEL, NODE_LABEL), ), panels.target( - f"sum(rate({metric('process_cpu_seconds_total')}[$__rate_interval])) by ({COMPONENT_LABEL}, {NODE_LABEL}) / avg({metric('process_cpu_core_num')}) by ({COMPONENT_LABEL}, {NODE_LABEL})", + f"sum(rate({metric('process_cpu_seconds_total')}[$__rate_interval])) by ({COMPONENT_LABEL}, {NODE_LABEL}) / avg({metric('process_cpu_core_num')}) by ({COMPONENT_LABEL}, {NODE_LABEL}) > 0", "cpu usage (avg per core) - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), ], ), + panels.timeseries_cpu( + "Node CPU relative", + "CPU usage relative to k8s resource limit of container. Only works in K8s environment", + [ + panels.target( + "(sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\",container=~\"$component\",pod=~\"$pod\"}[$__rate_interval])) by (namespace, pod)) / (sum(kube_pod_container_resource_limits{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\", resource=\"cpu\"}) by (namespace, pod))", + "cpu usage @ {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + ], + ), panels.timeseries_count( "Meta Cluster", "RW cluster can configure multiple meta nodes to achieve high availability. One is the leader and the " @@ -146,7 +175,7 @@ def section_recovery_node(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by (le) (rate({metric('recovery_latency_sum')}[$__rate_interval])) / sum by (le) (rate({metric('recovery_latency_count')}[$__rate_interval]))", + f"sum by (le) (rate({metric('recovery_latency_sum')}[$__rate_interval])) / sum by (le) (rate({metric('recovery_latency_count')}[$__rate_interval])) > 0", "recovery latency avg", ), ], @@ -230,7 +259,7 @@ def section_compaction(outer_panels): "Avg l0 select_level_count of the compact task, and categorize it according to different cg, levels and task types", [ panels.target( - f"sum by(le, group, type)(irate({metric('storage_l0_compact_level_count_sum')}[$__rate_interval])) / sum by(le, group, type)(irate({metric('storage_l0_compact_level_count_count')}[$__rate_interval]))", + f"sum by(le, group, type)(irate({metric('storage_l0_compact_level_count_sum')}[$__rate_interval])) / sum by(le, group, type)(irate({metric('storage_l0_compact_level_count_count')}[$__rate_interval])) > 0", "avg cg{{group}}@{{type}}", ), ], @@ -240,7 +269,7 @@ def section_compaction(outer_panels): "Avg file count of the compact task, and categorize it according to different cg, levels and task types", [ panels.target( - f"sum by(le, group, type)(irate({metric('storage_compact_task_file_count_sum')}[$__rate_interval])) / sum by(le, group, type)(irate({metric('storage_compact_task_file_count_count')}[$__rate_interval]))", + f"sum by(le, group, type)(irate({metric('storage_compact_task_file_count_sum')}[$__rate_interval])) / sum by(le, group, type)(irate({metric('storage_compact_task_file_count_count')}[$__rate_interval])) > 0", "avg cg{{group}}@{{type}}", ), ], @@ -315,11 +344,11 @@ def section_compaction(outer_panels): "compute_apply_version_duration_p99", ), panels.target( - f"sum by(le)(rate({metric('compactor_compact_task_duration_sum')}[$__rate_interval])) / sum by(le)(rate({metric('compactor_compact_task_duration_count')}[$__rate_interval]))", + f"sum by(le)(rate({metric('compactor_compact_task_duration_sum')}[$__rate_interval])) / sum by(le)(rate({metric('compactor_compact_task_duration_count')}[$__rate_interval])) > 0", "compact-task avg", ), panels.target( - f"sum by(le)(rate({metric('state_store_compact_sst_duration_sum')}[$__rate_interval])) / sum by(le)(rate({metric('state_store_compact_sst_duration_count')}[$__rate_interval]))", + f"sum by(le)(rate({metric('state_store_compact_sst_duration_sum')}[$__rate_interval])) / sum by(le)(rate({metric('state_store_compact_sst_duration_count')}[$__rate_interval])) > 0", "compact-key-range avg", ), ], @@ -371,7 +400,7 @@ def section_compaction(outer_panels): "may read this piece of data and write it to a new SSTable, that's another write.", [ panels.target( - f"sum({metric('storage_level_compact_write')}) / sum({metric('compactor_write_build_l0_bytes')})", + f"sum({metric('storage_level_compact_write')}) / sum({metric('compactor_write_build_l0_bytes')}) > 0", "write amplification", ), ], @@ -435,12 +464,12 @@ def section_compaction(outer_panels): "Total bytes gotten from sstable_bloom_filter, for observing bloom_filter size", [ panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_bloom_filter_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_bloom_filter_size_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_bloom_filter_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_bloom_filter_size_count')}[$__rate_interval])) > 0", "avg_meta - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_file_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_file_size_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_file_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_file_size_count')}[$__rate_interval])) > 0", "avg_file - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -451,7 +480,7 @@ def section_compaction(outer_panels): "Total bytes gotten from sstable_avg_key_size, for observing sstable_avg_key_size", [ panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_avg_key_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_avg_key_size_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_avg_key_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_avg_key_size_count')}[$__rate_interval])) > 0", "avg_key_size - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -467,7 +496,7 @@ def section_compaction(outer_panels): "Avg count gotten from sstable_distinct_epoch_count, for observing sstable_distinct_epoch_count", [ panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_distinct_epoch_count_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_distinct_epoch_count_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_distinct_epoch_count_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('compactor_sstable_distinct_epoch_count_count')}[$__rate_interval])) > 0", "avg_epoch_count - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -532,8 +561,8 @@ def section_object_storage(outer_panels): operation_duration_blacklist = "type!~'streaming_upload_write_bytes|streaming_read'" write_op_filter = "type=~'upload|delete'" read_op_filter = "type=~'read|readv|list|metadata'" - request_cost_op1 = "type=~'read|streaming_read_start|delete'" - request_cost_op2 = "type=~'upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|delete_objects|list'" + s3_request_cost_op1 = "type=~'read|streaming_read_start|streaming_read_init'" + s3_request_cost_op2 = "type=~'upload|streaming_upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|list'" return [ outer_panels.row_collapsed( "Object Storage", @@ -566,7 +595,7 @@ def section_object_storage(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, type, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('object_store_operation_latency_sum', operation_duration_blacklist)}[$__rate_interval])) / sum by(le, type, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('object_store_operation_latency_count')}[$__rate_interval]))", + f"sum by(le, type, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('object_store_operation_latency_sum', operation_duration_blacklist)}[$__rate_interval])) / sum by(le, type, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('object_store_operation_latency_count')}[$__rate_interval])) > 0", "{{type}} avg - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -622,12 +651,7 @@ def section_object_storage(outer_panels): "", [ panels.target( - f"sum(irate({metric('aws_sdk_retry_counts')}[$__rate_interval])) by ({NODE_LABEL}, {COMPONENT_LABEL}, type)", - "{{type}} - {{%s}} @ {{%s}}" - % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"sum(irate({metric('s3_read_request_retry_count')}[$__rate_interval])) by ({NODE_LABEL}, {COMPONENT_LABEL}, type)", + f"sum(rate({metric('object_store_request_retry_count')}[$__rate_interval])) by ({NODE_LABEL}, {COMPONENT_LABEL}, type)", "{{type}} - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -646,11 +670,11 @@ def section_object_storage(outer_panels): True, ), panels.target( - f"sum({metric('object_store_operation_latency_count', request_cost_op1)}) * 0.0004 / 1000", + f"sum({metric('object_store_operation_latency_count', s3_request_cost_op1)}) * 0.0004 / 1000", "GET, SELECT, and all other Requests Cost", ), panels.target( - f"sum({metric('object_store_operation_latency_count', request_cost_op2)}) * 0.005 / 1000", + f"sum({metric('object_store_operation_latency_count', s3_request_cost_op2)}) * 0.005 / 1000", "PUT, COPY, POST, LIST Requests Cost", ), ], @@ -703,7 +727,7 @@ def section_streaming(outer_panels): ) + [ panels.target( - f"rate({metric('meta_barrier_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_duration_seconds_count')}[$__rate_interval])", + f"rate({metric('meta_barrier_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_duration_seconds_count')}[$__rate_interval]) > 0", "barrier_latency_avg", ), ], @@ -735,7 +759,7 @@ def section_streaming(outer_panels): [ panels.target( f"rate({metric('source_partition_input_count')}[$__rate_interval])", - "actor={{actor_id}} source={{source_id}} partition={{partition}} fragmend_id={{fragment_id}}", + "actor={{actor_id}} source={{source_id}} partition={{partition}} fragment_id={{fragment_id}}", ) ], ), @@ -756,7 +780,7 @@ def section_streaming(outer_panels): [ panels.target( f"(rate({metric('source_partition_input_bytes')}[$__rate_interval]))/(1000*1000)", - "actor={{actor_id}} source={{source_id}} partition={{partition}} fragmend_id={{fragment_id}}", + "actor={{actor_id}} source={{source_id}} partition={{partition}} fragment_id={{fragment_id}}", ) ], ), @@ -837,8 +861,8 @@ def section_streaming(outer_panels): "The figure shows the number of rows written into each materialized view per second.", [ panels.target( - f"sum(rate({metric('stream_mview_input_row_count')}[$__rate_interval])) by (fragment_id, table_id) * on(fragment_id, table_id) group_left(table_name) {metric('table_info')}", - "mview {{table_id}} {{table_name}} - fragment_id {{fragment_id}}", + f"rate({metric('stream_mview_input_row_count')}[$__rate_interval]) * on(fragment_id, table_id) group_left(table_name) {metric('table_info')}", + "mview {{table_id}} {{table_name}} - actor {{actor_id}} fragment_id {{fragment_id}}", ), ], ), @@ -878,7 +902,7 @@ def section_streaming(outer_panels): ) + [ panels.target( - f"rate({metric('meta_barrier_send_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_send_duration_seconds_count')}[$__rate_interval])", + f"rate({metric('meta_barrier_send_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_send_duration_seconds_count')}[$__rate_interval]) > 0", "barrier_send_latency_avg", ), ], @@ -895,7 +919,7 @@ def section_streaming(outer_panels): ) + [ panels.target( - f"max(sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_inflight_duration_seconds_sum')}[$__rate_interval])) / sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_inflight_duration_seconds_count')}[$__rate_interval])))", + f"max(sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_inflight_duration_seconds_sum')}[$__rate_interval])) / sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_inflight_duration_seconds_count')}[$__rate_interval]))) > 0", "barrier_inflight_latency_avg", ), ], @@ -913,7 +937,7 @@ def section_streaming(outer_panels): ) + [ panels.target( - f"sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_sync_storage_duration_seconds_sum')}[$__rate_interval])) / sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_sync_storage_duration_seconds_count')}[$__rate_interval]))", + f"sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_sync_storage_duration_seconds_sum')}[$__rate_interval])) / sum by(le, {NODE_LABEL})(rate({metric('stream_barrier_sync_storage_duration_seconds_count')}[$__rate_interval])) > 0", "barrier_sync_latency_avg - {{%s}}" % NODE_LABEL, ), ], @@ -930,7 +954,7 @@ def section_streaming(outer_panels): ) + [ panels.target( - f"rate({metric('meta_barrier_wait_commit_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_wait_commit_duration_seconds_count')}[$__rate_interval])", + f"rate({metric('meta_barrier_wait_commit_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_wait_commit_duration_seconds_count')}[$__rate_interval]) > 0", "barrier_wait_commit_avg", ), ], @@ -1162,66 +1186,84 @@ def section_streaming_actors(outer_panels): "", [ panels.target( - f"(sum(rate({metric('stream_join_lookup_miss_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate({metric('stream_join_lookup_total_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id))", + f"(sum(rate({metric('stream_join_lookup_miss_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate({metric('stream_join_lookup_total_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id)) >= 0", "Join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} fragment {{fragment_id}}", ), panels.target( - f"(sum(rate({metric('stream_agg_lookup_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_agg_lookup_total_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_agg_lookup_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_agg_lookup_total_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"(sum(rate({metric('stream_agg_distinct_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_agg_distinct_total_cache_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_agg_distinct_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_agg_distinct_total_cache_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Distinct agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"(sum(rate({metric('stream_group_top_n_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_group_top_n_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_group_top_n_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_group_top_n_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Stream group top n cache miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"(sum(rate({metric('stream_group_top_n_appendonly_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_group_top_n_appendonly_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_group_top_n_appendonly_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_group_top_n_appendonly_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Stream group top n appendonly cache miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"(sum(rate({metric('stream_lookup_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_lookup_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_lookup_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_lookup_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Stream lookup cache miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"(sum(rate({metric('stream_temporal_join_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_temporal_join_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_temporal_join_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_temporal_join_total_query_cache_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Stream temporal join cache miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"1 - (sum(rate({metric('stream_materialize_cache_hit_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_materialize_cache_total_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"1 - (sum(rate({metric('stream_materialize_cache_hit_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_materialize_cache_total_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Materialize executor cache miss ratio - table {{table_id}} fragment {{fragment_id}} {{%s}}" % NODE_LABEL, ), panels.target( - f"(sum(rate({metric('stream_over_window_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_over_window_cache_lookup_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_over_window_cache_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_over_window_cache_lookup_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Over window cache miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"(sum(rate({metric('stream_over_window_range_cache_left_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_over_window_range_cache_lookup_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_over_window_range_cache_left_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_over_window_range_cache_lookup_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Over window partition range cache left miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), panels.target( - f"(sum(rate({metric('stream_over_window_range_cache_right_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_over_window_range_cache_lookup_count')}[$__rate_interval])) by (table_id, fragment_id))", + f"(sum(rate({metric('stream_over_window_range_cache_right_miss_count')}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate({metric('stream_over_window_range_cache_lookup_count')}[$__rate_interval])) by (table_id, fragment_id)) >= 0", "Over window partition range cache right miss ratio - table {{table_id}} fragment {{fragment_id}} ", ), ], ), panels.timeseries_actor_latency( - "Join Executor Barrier Align", + "Executor Barrier Align", "", [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({metric('stream_join_barrier_align_duration_bucket')}[$__rate_interval])) by (le, fragment_id, wait_side, {COMPONENT_LABEL}))", - f"p{legend} - fragment {{{{fragment_id}}}} {{{{wait_side}}}} - {{{{{COMPONENT_LABEL}}}}}", + f"histogram_quantile({quantile}, sum(rate({metric('stream_barrier_align_duration_bucket')}[$__rate_interval])) by (le, executor, fragment_id, wait_side, {COMPONENT_LABEL}))", + f"p{legend} - executor {{{{executor}}}} fragment {{{{fragment_id}}}} {{{{wait_side}}}} - {{{{{COMPONENT_LABEL}}}}}", ), [90, 99, 999, "max"], ), panels.target( - f"sum by(le, fragment_id, wait_side, job)(rate({metric('stream_join_barrier_align_duration_sum')}[$__rate_interval])) / sum by(le,fragment_id,wait_side,{COMPONENT_LABEL}) (rate({metric('stream_join_barrier_align_duration_count')}[$__rate_interval]))", - "avg - fragment {{fragment_id}} {{wait_side}} - {{%s}}" + f"sum by(le, executor, fragment_id, wait_side, job)(rate({metric('stream_barrier_align_duration_sum')}[$__rate_interval])) / sum by(le,executor,fragment_id,wait_side,{COMPONENT_LABEL}) (rate({metric('stream_barrier_align_duration_count')}[$__rate_interval])) > 0", + "avg - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{%s}}" + % COMPONENT_LABEL, + ), + ], + ), + panels.timeseries_actor_latency( + "Merger Barrier Align", + "", + [ + *quantile( + lambda quantile, legend: panels.target( + f"histogram_quantile({quantile}, sum(rate({metric('stream_merge_barrier_align_duration_bucket')}[$__rate_interval])) by (le, fragment_id, {COMPONENT_LABEL}))", + f"p{legend} - fragment {{{{fragment_id}}}} - {{{{{COMPONENT_LABEL}}}}}", + ), + [90, 99, 999, "max"], + ), + panels.target( + f"sum by(le, fragment_id, job)(rate({metric('stream_merge_barrier_align_duration_sum')}[$__rate_interval])) / sum by(le,fragment_id,{COMPONENT_LABEL}) (rate({metric('stream_merge_barrier_align_duration_count')}[$__rate_interval])) > 0", + "avg - fragment {{fragment_id}} - {{%s}}" % COMPONENT_LABEL, ), ], @@ -1274,14 +1316,14 @@ def section_streaming_actors(outer_panels): "The number of matched rows on the opposite side", [ *quantile( - lambda quantile, legend: panels.target( + lambda quantile, legend: panels.target_hidden( f"histogram_quantile({quantile}, sum(rate({metric('stream_join_matched_join_keys_bucket')}[$__rate_interval])) by (le, fragment_id, table_id, {COMPONENT_LABEL}))", f"p{legend} - fragment {{{{fragment_id}}}} table_id {{{{table_id}}}} - {{{{{COMPONENT_LABEL}}}}}", ), [90, 99, "max"], ), panels.target( - f"sum by(le, job, actor_id, table_id) (rate({metric('stream_join_matched_join_keys_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, fragment_id, table_id) (rate({table_metric('stream_join_matched_join_keys_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, fragment_id, table_id) (rate({metric('stream_join_matched_join_keys_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, fragment_id, table_id) (rate({table_metric('stream_join_matched_join_keys_count')}[$__rate_interval])) >= 0", "avg - fragment {{fragment_id}} table_id {{table_id}} - {{%s}}" % COMPONENT_LABEL, ), @@ -1766,12 +1808,26 @@ def section_batch(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('batch_row_seq_scan_next_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('batch_row_seq_scan_next_duration_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('batch_row_seq_scan_next_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('batch_row_seq_scan_next_duration_count')}[$__rate_interval])) > 0", "row_seq_scan next avg - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), ], ), + panels.timeseries_bytes_per_sec( + "Batch Spill Throughput", + "Disk throughputs of spilling-out in the bacth query engine", + [ + panels.target( + f"sum(rate({metric('batch_spill_read_bytes')}[$__rate_interval]))by({COMPONENT_LABEL}, {NODE_LABEL})", + "read - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), + ), + panels.target( + f"sum(rate({metric('batch_spill_write_bytes')}[$__rate_interval]))by({COMPONENT_LABEL}, {NODE_LABEL})", + "write - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), + ), + ], + ), ], ), ] @@ -1850,17 +1906,14 @@ def section_frontend(outer_panels): "Query Latency (Distributed Query Mode)", "", [ - panels.target( - f"histogram_quantile(0.5, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p50 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.9, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p90 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.95, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p99 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), + *quantile( + lambda quantile, legend: panels.target( + f"histogram_quantile({quantile}, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", + f"p{legend}" + + " - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + [50, 90, 99, "max"], ), ], ), @@ -1868,17 +1921,14 @@ def section_frontend(outer_panels): "Query Latency (Local Query Mode)", "", [ - panels.target( - f"histogram_quantile(0.5, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p50 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.9, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p90 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.95, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p99 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), + *quantile( + lambda quantile, legend: panels.target( + f"histogram_quantile({quantile}, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", + f"p{legend}" + + " - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + [50, 90, 99, "max"], ), ], ), @@ -1930,6 +1980,11 @@ def section_hummock_read(outer_panels): "data cache - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), + panels.target( + f"avg({metric('state_store_prefetch_memory_size')}) by ({COMPONENT_LABEL}, {NODE_LABEL})", + "prefetch cache - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), ], ), panels.timeseries_percentage( @@ -1937,17 +1992,31 @@ def section_hummock_read(outer_panels): "", [ panels.target( - f"(sum(rate({table_metric('state_store_sst_store_block_request_counts', meta_miss_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id)) / (sum(rate({table_metric('state_store_sst_store_block_request_counts', meta_total_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id))", + f"(sum(rate({table_metric('state_store_sst_store_block_request_counts', meta_miss_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id)) / (sum(rate({table_metric('state_store_sst_store_block_request_counts', meta_total_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id)) >= 0", "meta cache miss ratio - {{table_id}} @ {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), panels.target( - f"(sum(rate({table_metric('state_store_sst_store_block_request_counts', data_miss_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id)) / (sum(rate({table_metric('state_store_sst_store_block_request_counts', data_total_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id))", + f"(sum(rate({table_metric('state_store_sst_store_block_request_counts', data_miss_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id)) / (sum(rate({table_metric('state_store_sst_store_block_request_counts', data_total_filter)}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id)) >= 0", "block cache miss ratio - {{table_id}} @ {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), ], ), + panels.timeseries_percentage( + "Block Cache Efficiency", + "Histogram of the estimated hit ratio of a block while in the block cache.", + [ + *quantile( + lambda quantile, legend: panels.target( + f"clamp_max(histogram_quantile({quantile}, sum(rate({metric('block_efficiency_histogram_bucket')}[$__rate_interval])) by (le,{COMPONENT_LABEL},{NODE_LABEL})), 1)", + f"block cache efficienfy - p{legend}" + + " - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), + ), + [10, 25, 50, 75, 90, 100], + ), + ] + ), panels.timeseries_ops( "Iter keys flow", "", @@ -1973,7 +2042,7 @@ def section_hummock_read(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_iter_merge_sstable_counts_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_iter_merge_sstable_counts_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_iter_merge_sstable_counts_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_iter_merge_sstable_counts_count')}[$__rate_interval])) > 0", "# merged ssts avg - {{table_id}} @ {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -1993,7 +2062,7 @@ def section_hummock_read(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_get_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id) (rate({table_metric('state_store_get_duration_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_get_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id) (rate({table_metric('state_store_get_duration_count')}[$__rate_interval])) > 0", "avg - {{table_id}} {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -2006,26 +2075,26 @@ def section_hummock_read(outer_panels): [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_init_duration_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id))", - f"create_iter_time p{legend} - {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", + f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_init_duration_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id, iter_type))", + f"create_iter_time p{legend} - {{{{iter_type}}}} {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", ), [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_iter_init_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_iter_init_duration_count')}[$__rate_interval]))", - "create_iter_time avg - {{%s}} @ {{%s}}" + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_iter_init_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, iter_type) (rate({metric('state_store_iter_init_duration_count')}[$__rate_interval])) > 0", + "create_iter_time avg - {{iter_type}} {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_scan_duration_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id))", - f"pure_scan_time p{legend} - {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", + f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_scan_duration_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id, iter_type))", + f"pure_scan_time p{legend} - {{{{iter_type}}}} {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", ), [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_iter_scan_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_iter_scan_duration_count')}[$__rate_interval]))", - "pure_scan_time avg - {{%s}} @ {{%s}}" + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_iter_scan_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, iter_type) (rate({metric('state_store_iter_scan_duration_count')}[$__rate_interval])) > 0", + "pure_scan_time avg - {{iter_type}} {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), ], @@ -2053,7 +2122,7 @@ def section_hummock_read(outer_panels): "Positive / Total", [ panels.target( - f"(sum(rate({table_metric('state_store_read_req_bloom_filter_positive_counts')}[$__rate_interval])) by (table_id,type)) / (sum(rate({table_metric('state_store_read_req_check_bloom_filter_counts')}[$__rate_interval])) by (table_id,type))", + f"(sum(rate({table_metric('state_store_read_req_bloom_filter_positive_counts')}[$__rate_interval])) by (table_id,type)) / (sum(rate({table_metric('state_store_read_req_check_bloom_filter_counts')}[$__rate_interval])) by (table_id,type)) >= 0", "bloom filter positive rate - {{table_id}} - {{type}}", ), ], @@ -2063,7 +2132,7 @@ def section_hummock_read(outer_panels): "False-Positive / Total", [ panels.target( - f"(((sum(rate({table_metric('state_store_read_req_positive_but_non_exist_counts')}[$__rate_interval])) by (table_id,type))) / (sum(rate({table_metric('state_store_read_req_check_bloom_filter_counts')}[$__rate_interval])) by (table_id,type)))", + f"(((sum(rate({table_metric('state_store_read_req_positive_but_non_exist_counts')}[$__rate_interval])) by (table_id,type))) / (sum(rate({table_metric('state_store_read_req_check_bloom_filter_counts')}[$__rate_interval])) by (table_id,type))) >= 0", "read req bloom filter false positive rate - {{table_id}} - {{type}}", ), ], @@ -2093,8 +2162,8 @@ def section_hummock_read(outer_panels): % (COMPONENT_LABEL, NODE_LABEL), ), panels.target( - f"sum(rate({table_metric('state_store_iter_in_process_counts')}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id)", - "iter - {{table_id}} @ {{%s}} @ {{%s}}" + f"sum(rate({table_metric('state_store_iter_counts')}[$__rate_interval])) by ({COMPONENT_LABEL},{NODE_LABEL},table_id, iter_type)", + "{{iter_type}} - {{table_id}} @ {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), ], @@ -2118,8 +2187,8 @@ def section_hummock_read(outer_panels): [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_size_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id))", - f"p{legend} - {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", + f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_size_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id, iter_type))", + f"p{legend} - {{{{iter_type}}}} {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", ), [50, 99, "max"], ), @@ -2144,11 +2213,19 @@ def section_hummock_read(outer_panels): [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_item_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id))", - f"p{legend} - {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", + f"histogram_quantile({quantile}, sum(rate({table_metric('state_store_iter_item_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id, iter_type))", + f"p{legend} - {{{{iter_type}}}} {{{{table_id}}}} @ {{{{{COMPONENT_LABEL}}}}} @ {{{{{NODE_LABEL}}}}}", ), [50, 99, "max"], ), + panels.target( + f"{metric('state_store_iter_in_progress_counts')}", + "Existing {{iter_type}} count @ {{table_id}}", + ), + panels.target( + f"sum(rate({metric('state_store_iter_log_op_type_counts')}[$__rate_interval])) by (table_id, op_type)", + "iter_log op count @ {{table_id}} {{op_type}}", + ), ], ), panels.timeseries_bytes_per_sec( @@ -2188,7 +2265,7 @@ def section_hummock_read(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id) (rate({table_metric('state_store_iter_fetch_meta_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id) (rate({table_metric('state_store_iter_fetch_meta_duration_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id) (rate({table_metric('state_store_iter_fetch_meta_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id) (rate({table_metric('state_store_iter_fetch_meta_duration_count')}[$__rate_interval])) > 0", "fetch_meta_duration avg - {{table_id}} @ {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -2229,6 +2306,28 @@ def section_hummock_write(outer_panels): "uploading task size - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), + panels.target( + f"sum({metric('state_store_uploader_imm_size')}) by ({COMPONENT_LABEL}, {NODE_LABEL})", + "uploader imm size - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + panels.target( + f"sum({metric('state_store_uploader_imm_size')}) by ({COMPONENT_LABEL}, {NODE_LABEL}) - " + f"sum({metric('state_store_uploader_uploading_task_size')}) by ({COMPONENT_LABEL}, {NODE_LABEL})", + "unflushed imm size - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + panels.target( + f"sum({metric('uploading_memory_size')}) by ({COMPONENT_LABEL}, {NODE_LABEL}) - " + f"sum({metric('state_store_uploader_imm_size')}) by ({COMPONENT_LABEL}, {NODE_LABEL})", + "orphan imm size - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + panels.target( + f"sum({metric('state_store_old_value_size')}) by ({COMPONENT_LABEL}, {NODE_LABEL})", + "old value size - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), ], ), panels.timeseries_latency( @@ -2244,7 +2343,7 @@ def section_hummock_write(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_duration_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_duration_count')}[$__rate_interval])) > 0", "avg Sync duration - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), *quantile( @@ -2340,7 +2439,7 @@ def section_hummock_write(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_write_batch_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_write_batch_duration_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_write_batch_duration_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}, table_id)(rate({table_metric('state_store_write_batch_duration_count')}[$__rate_interval])) > 0", "write to shared_buffer avg - {{table_id}} @ {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -2353,7 +2452,7 @@ def section_hummock_write(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_write_shared_buffer_sync_time_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_write_shared_buffer_sync_time_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_write_shared_buffer_sync_time_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL})(rate({metric('state_store_write_shared_buffer_sync_time_count')}[$__rate_interval])) > 0", "write to object_store - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -2375,12 +2474,12 @@ def section_hummock_write(outer_panels): "", [ panels.target( - f"sum(rate({table_metric('state_store_write_batch_size_sum')}[$__rate_interval]))by({COMPONENT_LABEL},{NODE_LABEL},table_id) / sum(rate({table_metric('state_store_write_batch_size_count')}[$__rate_interval]))by({COMPONENT_LABEL},{NODE_LABEL},table_id)", + f"sum(rate({table_metric('state_store_write_batch_size_sum')}[$__rate_interval]))by({COMPONENT_LABEL},{NODE_LABEL},table_id) / sum(rate({table_metric('state_store_write_batch_size_count')}[$__rate_interval]))by({COMPONENT_LABEL},{NODE_LABEL},table_id) > 0", "shared_buffer - {{table_id}} @ {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), panels.target( - f"sum(rate({metric('compactor_shared_buffer_to_sstable_size_sum')}[$__rate_interval]))by({COMPONENT_LABEL}, {NODE_LABEL}) / sum(rate({metric('compactor_shared_buffer_to_sstable_size_count')}[$__rate_interval]))by({COMPONENT_LABEL}, {NODE_LABEL})", + f"sum(rate({metric('compactor_shared_buffer_to_sstable_size_sum')}[$__rate_interval]))by({COMPONENT_LABEL}, {NODE_LABEL}) / sum(rate({metric('compactor_shared_buffer_to_sstable_size_count')}[$__rate_interval]))by({COMPONENT_LABEL}, {NODE_LABEL}) > 0", "sync - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), ], @@ -2395,7 +2494,7 @@ def section_hummock_write(outer_panels): % (COMPONENT_LABEL, NODE_LABEL), ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_write_batch_size_sum')}[$__rate_interval])) / sum by(le, table_id, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_write_batch_size_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_write_batch_size_sum')}[$__rate_interval])) / sum by(le, table_id, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_write_batch_size_count')}[$__rate_interval])) > 0", "avg - {{table_id}} {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), @@ -2425,7 +2524,7 @@ def section_hummock_write(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_size_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('state_store_sync_size_count')}[$__rate_interval])) > 0", "avg - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), ), ], @@ -2482,158 +2581,243 @@ def section_hummock_tiered_cache(outer_panels): block_refill_filter = 'type="block"' block_refill_success_filter = 'type="block",op="success"' block_refill_unfiltered_filter = 'type="block",op="unfiltered"' + cache_hit_filter = 'op="hit"' + cache_miss_filter = 'op="miss"' return [ outer_panels.row_collapsed( "Hummock Tiered Cache", [ + # hybrid panels.timeseries_ops( - "Ops", + "Hybrid Cache Ops", "", [ panels.target( - f"sum(rate({metric('foyer_storage_op_duration_count')}[$__rate_interval])) by (foyer, op, extra, {NODE_LABEL})", - "{{foyer}} file cache {{op}} {{extra}} @ {{%s}}" + f"sum(rate({metric('foyer_hybrid_op_total')}[$__rate_interval])) by (name, op, {NODE_LABEL})", + "{{name}} - hybrid - {{op}} @ {{%s}}" % NODE_LABEL, ), ], ), panels.timeseries_latency( - "Duration", + "Hybrid Cache Op Duration", "", [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({metric('foyer_storage_op_duration_bucket')}[$__rate_interval])) by (le, foyer, op, extra, {NODE_LABEL}))", + f"histogram_quantile({quantile}, sum(rate({metric('foyer_hybrid_op_duration_bucket')}[$__rate_interval])) by (le, name, op, {NODE_LABEL}))", f"p{legend}" - + " - {{foyer}} file cache - {{op}} {{extra}} @ {{%s}}" + + " - {{name}} - hybrid - {{op}} @ {{%s}}" % NODE_LABEL, ), [50, 90, 99, "max"], ), ], ), - panels.timeseries_bytes_per_sec( - "Throughput", + panels.timeseries_percentage( + "Hybrid Cache Hit Ratio", "", [ panels.target( - f"sum(rate({metric('foyer_storage_op_bytes')}[$__rate_interval])) by (foyer, op, extra, {NODE_LABEL})", - "{{foyer}} file cache - {{op}} {{extra}} @ {{%s}}" + f"sum(rate({metric('foyer_hybrid_op_total', cache_hit_filter)}[$__rate_interval])) by (name, {NODE_LABEL}) / (sum(rate({metric('foyer_hybrid_op_total', cache_hit_filter)}[$__rate_interval])) by (name, {NODE_LABEL}) + sum(rate({metric('foyer_hybrid_op_total', cache_miss_filter)}[$__rate_interval])) by (name, {NODE_LABEL}))", + "{{name}} - hybrid - hit ratio @ {{%s}}" % NODE_LABEL, + ), + ], + ), + # memory + panels.timeseries_ops( + "Memory Cache Ops", + "", + [ + panels.target( + f"sum(rate({metric('foyer_memory_op_total')}[$__rate_interval])) by (name, op, {NODE_LABEL})", + "{{name}} - memory - {{op}} @ {{%s}}" % NODE_LABEL, ), ], ), - panels.timeseries_percentage( - "Hit Ratio", + panels.timeseries_bytes( + "Memory Cache Size", "", [ panels.target( - f"sum(rate({metric('foyer_storage_op_duration_count', file_cache_hit_filter)}[$__rate_interval])) by (foyer, {NODE_LABEL}) / (sum(rate({metric('foyer_storage_op_duration_count', file_cache_hit_filter)}[$__rate_interval])) by (foyer, {NODE_LABEL}) + sum(rate({metric('foyer_storage_op_duration_count', file_cache_miss_filter)}[$__rate_interval])) by (foyer, {NODE_LABEL}))", - "{{foyer}} file cache hit ratio @ {{%s}}" % NODE_LABEL, + f"sum({metric('foyer_memory_usage')}) by (name, {NODE_LABEL})", + "{{name}} - memory - size @ {{%s}}" % NODE_LABEL, ), ], ), - panels.timeseries_ops( - "Refill Ops", + panels.timeseries_percentage( + "Memory Cache Hit Ratio", "", [ panels.target( - f"sum(rate({metric('refill_duration_count')}[$__rate_interval])) by (type, op, {NODE_LABEL})", - "{{type}} file cache refill - {{op}} @ {{%s}}" % NODE_LABEL, + f"sum(rate({metric('foyer_memory_op_total', cache_hit_filter)}[$__rate_interval])) by (name, {NODE_LABEL}) / (sum(rate({metric('foyer_memory_op_total', cache_hit_filter)}[$__rate_interval])) by (name, {NODE_LABEL}) + sum(rate({metric('foyer_memory_op_total', cache_miss_filter)}[$__rate_interval])) by (name, {NODE_LABEL}))", + "{{name}} - memory - hit ratio @ {{%s}}" % NODE_LABEL, ), + ], + ), + # storage + panels.timeseries_ops( + "Storage Cache Ops", + "", + [ panels.target( - f"sum(rate({metric('refill_total', refill_ops_filter)}[$__rate_interval])) by (type, op, {NODE_LABEL})", - "{{type}} file cache refill - {{op}} @ {{%s}}" % NODE_LABEL, + f"sum(rate({metric('foyer_storage_op_total')}[$__rate_interval])) by (name, op, {NODE_LABEL})", + "{{name}} - storage - {{op}} @ {{%s}}" + % NODE_LABEL, ), ], ), - panels.timeseries_bytes_per_sec( - "Data Refill Throughput", + panels.timeseries_ops( + "Storage Cache Inner Ops", "", [ panels.target( - f"sum(rate({metric('refill_bytes')}[$__rate_interval])) by (foyer, op, {NODE_LABEL})", - "{{type}} file cache - {{op}} @ {{%s}}" + f"sum(rate({metric('foyer_storage_inner_op_total')}[$__rate_interval])) by (name, op, {NODE_LABEL})", + "{{name}} - storage - {{op}} @ {{%s}}" % NODE_LABEL, ), ], ), panels.timeseries_latency( - "Refill Duration", + "Storage Cache Op Duration", "", [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({metric('refill_duration_bucket')}[$__rate_interval])) by (le, foyer, op, {NODE_LABEL}))", + f"histogram_quantile({quantile}, sum(rate({metric('foyer_storage_op_duration_bucket')}[$__rate_interval])) by (le, name, op, {NODE_LABEL}))", f"p{legend}" - + " - {{foyer}} cache refill - {{op}} @ {{%s}}" + + " - {{name}} - storage - {{op}} @ {{%s}}" % NODE_LABEL, ), [50, 90, 99, "max"], ), ], ), - panels.timeseries_count( - "Refill Queue Length", + panels.timeseries_latency( + "Storage Cache Inner Op Duration", + "", + [ + *quantile( + lambda quantile, legend: panels.target( + f"histogram_quantile({quantile}, sum(rate({metric('foyer_storage_inner_op_duration_bucket')}[$__rate_interval])) by (le, name, op, {NODE_LABEL}))", + f"p{legend}" + + " - {{name}} - storage - {{op}} @ {{%s}}" + % NODE_LABEL, + ), + [50, 90, 99, "max"], + ), + ], + ), + panels.timeseries_percentage( + "Storage Cache Hit Ratio", "", [ panels.target( - f"sum(refill_queue_total) by ({NODE_LABEL})", - "refill queue length @ {{%s}}" % NODE_LABEL, + f"sum(rate({metric('foyer_storage_op_total', cache_hit_filter)}[$__rate_interval])) by (name, {NODE_LABEL}) / (sum(rate({metric('foyer_storage_op_total', cache_hit_filter)}[$__rate_interval])) by (name, {NODE_LABEL}) + sum(rate({metric('foyer_storage_op_total', cache_miss_filter)}[$__rate_interval])) by (name, {NODE_LABEL}))", + "{{name}} - storage - hit ratio @ {{%s}}" % NODE_LABEL, ), ], ), panels.timeseries_bytes( - "Size", + "Storage Region Size", "", [ panels.target( - f"sum({metric('foyer_storage_total_bytes')}) by (foyer, {NODE_LABEL})", - "{{foyer}} size @ {{%s}}" % NODE_LABEL, + f"sum({metric('foyer_storage_region')}) by (name, type, {NODE_LABEL}) * on(name, {NODE_LABEL}) group_left() avg({metric('foyer_storage_region_size_bytes')}) by (name, type, {NODE_LABEL})", + "{{name}} - {{type}} region - size @ {{%s}}" % NODE_LABEL, + ), + ], + ), + # disk + panels.timeseries_ops( + "Disk Ops", + "", + [ + panels.target( + f"sum(rate({metric('foyer_storage_disk_io_total')}[$__rate_interval])) by (name, op, {NODE_LABEL})", + "{{name}} - disk - {{op}} @ {{%s}}" + % NODE_LABEL, ), ], ), panels.timeseries_latency( - "Inner Op Duration", + "Disk Op Duration", "", [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({metric('foyer_storage_inner_op_duration_bucket')}[$__rate_interval])) by (le, foyer, op, extra, {NODE_LABEL}))", + f"histogram_quantile({quantile}, sum(rate({metric('foyer_storage_disk_io_duration_bucket')}[$__rate_interval])) by (le, name, op, {NODE_LABEL}))", f"p{legend}" - + " - {{foyer}} file cache - {{op}} {{extra}} @ {{%s}}" + + " - {{name}} - disk - {{op}} @ {{%s}}" % NODE_LABEL, ), [50, 90, 99, "max"], ), ], ), + panels.timeseries_bytes_per_sec( + "Disk Op Throughput", + "", + [ + panels.target( + f"sum(rate({metric('foyer_storage_disk_io_bytes')}[$__rate_interval])) by (name, op, {NODE_LABEL})", + "{{name}} - disk - {{op}} @ {{%s}}" + % NODE_LABEL, + ), + ], + ), + # refill panels.timeseries_ops( - "Slow Ops", + "Refill Ops", + "", + [ + panels.target( + f"sum(rate({metric('refill_duration_count')}[$__rate_interval])) by (type, op, {NODE_LABEL})", + "{{type}} file cache refill - {{op}} @ {{%s}}" % NODE_LABEL, + ), + panels.target( + f"sum(rate({metric('refill_total', refill_ops_filter)}[$__rate_interval])) by (type, op, {NODE_LABEL})", + "{{type}} file cache refill - {{op}} @ {{%s}}" % NODE_LABEL, + ), + ], + ), + panels.timeseries_bytes_per_sec( + "Data Refill Throughput", "", [ panels.target( - f"sum(rate({metric('foyer_storage_slow_op_duration_count')}[$__rate_interval])) by (foyer, op, extra, {NODE_LABEL})", - "{{foyer}} file cache {{op}} {{extra}} @ {{%s}}" + f"sum(rate({metric('refill_bytes')}[$__rate_interval])) by (foyer, op, {NODE_LABEL})", + "{{type}} file cache - {{op}} @ {{%s}}" % NODE_LABEL, ), ], ), panels.timeseries_latency( - "Slow Op Duration", + "Refill Duration", "", [ *quantile( lambda quantile, legend: panels.target( - f"histogram_quantile({quantile}, sum(rate({metric('foyer_storage_slow_op_duration_bucket')}[$__rate_interval])) by (le, foyer, op, extra, {NODE_LABEL}))", + f"histogram_quantile({quantile}, sum(rate({metric('refill_duration_bucket')}[$__rate_interval])) by (le, foyer, op, {NODE_LABEL}))", f"p{legend}" - + " - {{foyer}} file cache - {{op}} {{extra}} @ {{%s}}" + + " - {{foyer}} cache refill - {{op}} @ {{%s}}" % NODE_LABEL, ), [50, 90, 99, "max"], ), ], ), + panels.timeseries_count( + "Refill Queue Length", + "", + [ + panels.target( + f"sum(refill_queue_total) by ({NODE_LABEL})", + "refill queue length @ {{%s}}" % NODE_LABEL, + ), + ], + ), panels.timeseries_ops( "Inheritance - Parent Meta Lookup Ops", "", @@ -2650,7 +2834,7 @@ def section_hummock_tiered_cache(outer_panels): "", [ panels.target( - f"sum(rate({metric('refill_total', inheritance_parent_lookup_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) / (sum(rate({metric('refill_total', inheritance_parent_lookup_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) + sum(rate({metric('refill_total', inheritance_parent_lookup_miss_filter)}[$__rate_interval])) by ({NODE_LABEL}))", + f"sum(rate({metric('refill_total', inheritance_parent_lookup_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) / (sum(rate({metric('refill_total', inheritance_parent_lookup_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) + sum(rate({metric('refill_total', inheritance_parent_lookup_miss_filter)}[$__rate_interval])) by ({NODE_LABEL})) >= 0", "parent meta lookup hit ratio @ {{%s}}" % NODE_LABEL, ), ], @@ -2671,7 +2855,7 @@ def section_hummock_tiered_cache(outer_panels): "", [ panels.target( - f"sum(rate({metric('refill_total', unit_inheritance_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) / (sum(rate({metric('refill_total', unit_inheritance_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) + sum(rate({metric('refill_total', unit_inheritance_miss_filter)}[$__rate_interval])) by ({NODE_LABEL}))", + f"sum(rate({metric('refill_total', unit_inheritance_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) / (sum(rate({metric('refill_total', unit_inheritance_hit_filter)}[$__rate_interval])) by ({NODE_LABEL}) + sum(rate({metric('refill_total', unit_inheritance_miss_filter)}[$__rate_interval])) by ({NODE_LABEL})) >= 0", "unit inheritance ratio @ {{%s}}" % NODE_LABEL, ), ], @@ -2692,11 +2876,31 @@ def section_hummock_tiered_cache(outer_panels): "", [ panels.target( - f"sum(rate({metric('refill_total', block_refill_success_filter)}[$__rate_interval])) by ({NODE_LABEL}) / sum(rate({metric('refill_total', block_refill_unfiltered_filter)}[$__rate_interval])) by ({NODE_LABEL})", + f"sum(rate({metric('refill_total', block_refill_success_filter)}[$__rate_interval])) by ({NODE_LABEL}) / sum(rate({metric('refill_total', block_refill_unfiltered_filter)}[$__rate_interval])) by ({NODE_LABEL}) >= 0", "block refill ratio @ {{%s}}" % NODE_LABEL, ), ], ), + panels.timeseries_count( + "Recent Filter Size", + "Item numbers of the recent filter.", + [ + panels.target( + f"sum({metric('recent_filter_items')}) by ({NODE_LABEL})", + "items @ {{%s}}" % NODE_LABEL, + ), + ], + ), + panels.timeseries_ops( + "Recent Filter Ops", + "", + [ + panels.target( + f"sum(rate({metric('recent_filter_ops')}[$__rate_interval])) by (op, {NODE_LABEL})", + "recent filter {{op}} @ {{%s}}" % NODE_LABEL, + ), + ], + ), ], ) ] @@ -2891,7 +3095,7 @@ def section_hummock_manager(outer_panels): ) + [ panels.target( - f"rate({metric('storage_version_checkpoint_latency_sum')}[$__rate_interval]) / rate({metric('storage_version_checkpoint_latency_count')}[$__rate_interval])", + f"rate({metric('storage_version_checkpoint_latency_sum')}[$__rate_interval]) / rate({metric('storage_version_checkpoint_latency_count')}[$__rate_interval]) > 0", "version_checkpoint_latency_avg", ), ], @@ -3051,7 +3255,7 @@ def grpc_metrics_target(panels, name, filter): f"{name}_p99", ), panels.target( - f"sum(irate({metric('meta_grpc_duration_seconds_sum', filter)}[$__rate_interval])) / sum(irate({metric('meta_grpc_duration_seconds_count', filter)}[$__rate_interval]))", + f"sum(irate({metric('meta_grpc_duration_seconds_sum', filter)}[$__rate_interval])) / sum(irate({metric('meta_grpc_duration_seconds_count', filter)}[$__rate_interval])) > 0", f"{name}_avg", ), ], @@ -3177,7 +3381,7 @@ def section_grpc_hummock_meta_client(outer_panels): "unpin_version_before_latency_p99 - {{%s}}" % NODE_LABEL, ), panels.target( - f"sum(irate({metric('state_store_unpin_version_before_latency_sum')}[$__rate_interval])) / sum(irate({metric('state_store_unpin_version_before_latency_count')}[$__rate_interval]))", + f"sum(irate({metric('state_store_unpin_version_before_latency_sum')}[$__rate_interval])) / sum(irate({metric('state_store_unpin_version_before_latency_count')}[$__rate_interval])) > 0", "unpin_version_before_latency_avg", ), panels.target( @@ -3203,7 +3407,7 @@ def section_grpc_hummock_meta_client(outer_panels): "pin_snapshot_latencyp90 - {{%s}}" % NODE_LABEL, ), panels.target( - f"sum(irate({metric('state_store_pin_snapshot_latency_sum')}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval]))", + f"sum(irate({metric('state_store_pin_snapshot_latency_sum')}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval])) > 0", "pin_snapshot_latency_avg", ), panels.target( @@ -3215,7 +3419,7 @@ def section_grpc_hummock_meta_client(outer_panels): "unpin_snapshot_latency_p99 - {{%s}}" % NODE_LABEL, ), panels.target( - f"sum(irate({metric('state_store_unpin_snapshot_latency_sum')}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval]))", + f"sum(irate({metric('state_store_unpin_snapshot_latency_sum')}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval])) > 0", "unpin_snapshot_latency_avg", ), panels.target( @@ -3251,7 +3455,7 @@ def section_grpc_hummock_meta_client(outer_panels): "get_new_sst_ids_latency_latency_p99 - {{%s}}" % NODE_LABEL, ), panels.target( - f"sum(irate({metric('state_store_get_new_sst_ids_latency_sum')}[$__rate_interval])) / sum(irate({metric('state_store_get_new_sst_ids_latency_count')}[$__rate_interval]))", + f"sum(irate({metric('state_store_get_new_sst_ids_latency_sum')}[$__rate_interval])) / sum(irate({metric('state_store_get_new_sst_ids_latency_count')}[$__rate_interval])) > 0", "get_new_sst_ids_latency_latency_avg", ), panels.target( @@ -3283,7 +3487,7 @@ def section_grpc_hummock_meta_client(outer_panels): "report_compaction_task_latency_p99 - {{%s}}" % NODE_LABEL, ), panels.target( - f"sum(irate({metric('state_store_report_compaction_task_latency_sum')}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval]))", + f"sum(irate({metric('state_store_report_compaction_task_latency_sum')}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval])) > 0", "report_compaction_task_latency_avg", ), panels.target( @@ -3610,7 +3814,7 @@ def section_iceberg_metrics(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, sink_id)(rate({metric('iceberg_write_latency_sum')}[$__rate_interval])) / sum by(le, type, job, instance) (rate({metric('iceberg_write_latency_count')}[$__rate_interval]))", + f"sum by(le, sink_id)(rate({metric('iceberg_write_latency_sum')}[$__rate_interval])) / sum by(le, type, job, instance) (rate({metric('iceberg_write_latency_count')}[$__rate_interval])) > 0", "avg @ {{sink_id}}", ), ], @@ -3666,22 +3870,26 @@ def section_memory_manager(outer_panels): ), ], ), - panels.timeseries_count( - "LRU manager watermark steps", + panels.timeseries( + "LRU manager eviction policy", "", [ panels.target( - f"{metric('lru_watermark_step')}", + f"{metric('lru_eviction_policy')}", "", ), ], ), - panels.timeseries_ms( - "LRU manager diff between watermark_time and now (ms)", - "watermark_time is the current lower watermark of cached data. physical_now is the current time of the machine. The diff (physical_now - watermark_time) shows how much data is cached.", + panels.timeseries( + "LRU manager sequence", + "", [ panels.target( - f"{metric('lru_physical_now_ms')} - {metric('lru_current_watermark_time_ms')}", + f"{metric('lru_latest_sequence')}", + "", + ), + panels.target( + f"{metric('lru_watermark_sequence')}", "", ), ], @@ -3810,7 +4018,7 @@ def section_sink_metrics(outer_panels): [50, 99, "max"], ), panels.target( - f"sum by(le, connector, sink_id)(rate({metric('sink_commit_duration_sum')}[$__rate_interval])) / sum by(le, type, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('sink_commit_duration_count')}[$__rate_interval]))", + f"sum by(le, connector, sink_id)(rate({metric('sink_commit_duration_sum')}[$__rate_interval])) / sum by(le, type, {COMPONENT_LABEL}, {NODE_LABEL}) (rate({metric('sink_commit_duration_count')}[$__rate_interval])) > 0", "avg - {{connector}} @ {{sink_id}}", ), ], @@ -3827,6 +4035,10 @@ def section_sink_metrics(outer_panels): f"{metric('log_store_latest_read_epoch')}", "latest read epoch @ {{connector}} {{sink_id}} {{executor_id}}", ), + panels.target( + f"{metric('kv_log_store_buffer_unconsumed_min_epoch')}", + "Kv log store uncomsuned min epoch @ {{connector}} {{sink_id}} {{executor_id}}", + ), ], ), panels.timeseries_latency( @@ -3840,6 +4052,16 @@ def section_sink_metrics(outer_panels): ), ], ), + panels.timeseries_percentage( + "Log Store Backpressure Ratio", + "", + [ + panels.target( + f"avg(rate({metric('log_store_reader_wait_new_future_duration_ns')}[$__rate_interval])) by (connector, sink_id, executor_id) / 1000000000", + "Backpressure @ {{connector}} {{sink_id}} {{executor_id}}", + ), + ], + ), panels.timeseries_latency( "Log Store Consume Persistent Log Lag", "", @@ -3933,6 +4155,24 @@ def section_sink_metrics(outer_panels): ), ], ), + panels.timeseries_count( + "Kv Log Store Buffer State", + "", + [ + panels.target( + f"{metric('kv_log_store_buffer_unconsumed_item_count')}", + "Unconsumed item count @ {{connector}} {{sink_id}} {{executor_id}}", + ), + panels.target( + f"{metric('kv_log_store_buffer_unconsumed_row_count')}", + "Unconsumed row count @ {{connector}} {{sink_id}} {{executor_id}}", + ), + panels.target( + f"{metric('kv_log_store_buffer_unconsumed_epoch_count')}", + "Unconsumed epoch count @ {{connector}} {{sink_id}} {{executor_id}}", + ), + ], + ), panels.timeseries_ops( "Kv Log Store Rewind Rate", "", @@ -3953,6 +4193,16 @@ def section_sink_metrics(outer_panels): ), ], ), + panels.timeseries_bytes( + "Chunk Buffer Size", + "Total size of chunks buffered in a barrier", + [ + panels.target( + f"sum({metric('stream_sink_chunk_buffer_size')}) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) {metric('sink_info')}", + "sink {{sink_id}} {{sink_name}} - actor {{actor_id}}", + ), + ], + ), ], ) ] @@ -4132,7 +4382,7 @@ def section_udf(outer_panels): "", [ panels.target( - f"sum(irate({metric('udf_input_chunk_rows_sum')}[$__rate_interval])) by (link, name, fragment_id) / sum(irate({metric('udf_input_chunk_rows_count')}[$__rate_interval])) by (link, name, fragment_id)", + f"sum(irate({metric('udf_input_chunk_rows_sum')}[$__rate_interval])) by (link, name, fragment_id) / sum(irate({metric('udf_input_chunk_rows_count')}[$__rate_interval])) by (link, name, fragment_id) > 0", "udf_input_chunk_rows_avg - {{link}} {{name}} {{fragment_id}}", ), ], @@ -4154,7 +4404,7 @@ def section_udf(outer_panels): "udf_latency_p99 - {{%s}}" % NODE_LABEL, ), panels.target( - f"sum(irate({metric('udf_latency_sum')}[$__rate_interval])) by ({COMPONENT_LABEL}, {NODE_LABEL}) / sum(irate({metric('udf_latency_count')}[$__rate_interval])) by ({COMPONENT_LABEL}, {NODE_LABEL})", + f"sum(irate({metric('udf_latency_sum')}[$__rate_interval])) by ({COMPONENT_LABEL}, {NODE_LABEL}) / sum(irate({metric('udf_latency_count')}[$__rate_interval])) by ({COMPONENT_LABEL}, {NODE_LABEL}) > 0", "udf_latency_avg - {{%s}}" % NODE_LABEL, ), panels.target( @@ -4162,7 +4412,7 @@ def section_udf(outer_panels): "udf_latency_p99_by_name - {{link}} {{name}} {{fragment_id}}", ), panels.target( - f"sum(irate({metric('udf_latency_sum')}[$__rate_interval])) by (link, name, fragment_id) / sum(irate({metric('udf_latency_count')}[$__rate_interval])) by (link, name, fragment_id)", + f"sum(irate({metric('udf_latency_sum')}[$__rate_interval])) by (link, name, fragment_id) / sum(irate({metric('udf_latency_count')}[$__rate_interval])) by (link, name, fragment_id) > 0", "udf_latency_avg_by_name - {{link}} {{name}} {{fragment_id}}", ), ], @@ -4195,6 +4445,20 @@ def section_udf(outer_panels): ), ], ), + panels.timeseries_bytes( + "UDF Memory Usage (bytes)", + "Currently only embedded JS UDF supports this. Others will always show 0.", + [ + panels.target( + f"sum({metric('udf_memory_usage')}) by ({COMPONENT_LABEL}, {NODE_LABEL})", + "udf_memory_usage - {{%s}}" % NODE_LABEL, + ), + panels.target( + f"sum({metric('udf_memory_usage')}) by (name, fragment_id)", + "udf_memory_usage - {{name}} {{fragment_id}}", + ), + ], + ), ], ) ] diff --git a/grafana/risingwave-dev-dashboard.json b/grafana/risingwave-dev-dashboard.json index 0ca7142657562..3e303bca36ae2 100644 --- a/grafana/risingwave-dev-dashboard.json +++ b/grafana/risingwave-dev-dashboard.json @@ -1 +1 @@ -{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dev Dashboard","editable":true,"gnetId":null,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":5,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (total) - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (avg per core) - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"RW cluster can configure multiple meta nodes to achieve high availability. One is the leader and the rest are the followers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(meta_num{job=~\"$job\",instance=~\"$node\"}) by (worker_addr,role)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_addr}} @ {{role}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Meta Cluster","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Cluster Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":9,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The rate of successful recovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery Successful Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of failed reocovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Failed recovery attempts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Time spent in a successful recovery attempt","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":12,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency pmax - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by (le) (rate(recovery_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by (le) (rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Recovery","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":3},"height":null,"hideTimeOverride":false,"id":13,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of barriers that have been ingested but not completely processed. This metric reflects the current level of congestion within the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all_barrier","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"in_flight_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"in_flight_barrier","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":15,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration from the last committed barrier's epoch time to the current time. This metric reflects the data freshness of the system. During this time, no new data has been committed.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":16,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"timestamp(last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}) - last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_pending_time","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier pending time (secs)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(rows).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":18,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_partition_input_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragmend_id={{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":19,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(MB/s).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragmend_id={{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Monitor each source upstream, 0 means the upstream is not normal, 1 means the source is ready.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Upstream Status","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Source Split Change Events frequency by source_id and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_split_change_event_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Split Change Events frequency(events/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka Consumer Lag Size by source_id, partition and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min(source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"} - on(source_id, partition) group_right() source_latest_message_id{job=~\"$job\",instance=~\"$node\"}, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka Consumer Lag Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}} - actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":27,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":28,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, table_id) * on(fragment_id, table_id) group_left(table_name) table_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}} - fragment_id {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration between the time point when the scheduled barrier needs to be sent and the time point when the barrier gets actually sent to all the compute nodes. Developers can thus detect any internal congestion.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":31,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_send_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_send_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Send Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":32,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"max(sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier In-Flight Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p999 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_pmax - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_avg - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Sync Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_wait_commit_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_wait_commit_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Wait Commit Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of actors that have processed the earliest in-flight barriers per second. This metric helps users to detect potential congestion or stuck in the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_barrier_manager_progress{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Earliest In-Flight Barrier Progress","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":4},"height":null,"hideTimeOverride":false,"id":36,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the cdc backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the cdc backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":38,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":39,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p50 - {{table_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p99 - {{table_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag pmax - {{table_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Consume Lag Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(cdc_source_error{job=~\"$job\",instance=~\"$node\"}) by (connector_name, source_id, error_msg)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{connector_name}}: {{error_msg}} ({{source_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Source Errors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming CDC","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":5},"height":null,"hideTimeOverride":false,"id":41,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_input_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":44,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":45,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The operator-level memory usage statistics collected by each LRU cache","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":46,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (table_id, desc)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} desc: {{desc}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Memory usage aggregated by materialized views","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage of Materialized Views","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialize Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_left_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache left miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_right_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache right miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":51,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize executor cache miss ratio - table {{table_id}} fragment {{fragment_id}} {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_left_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache left miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_right_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache right miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":52,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p999 - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_join_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, fragment_id, wait_side, job)(rate(stream_join_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,fragment_id,wait_side,job) (rate(stream_join_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Executor Barrier Align","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":54,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id,side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Match Duration Per Second","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Multiple rows with distinct primary keys may have the same join key. This metric counts the number of join keys in the executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":55,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (fragment_id, side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of matched rows on the opposite side","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":56,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, actor_id, table_id) (rate(stream_join_matched_join_keys_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Executor Matched Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":57,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} actor {{actor_id}}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Executor Cache Statistics For Each StreamChunk","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":58,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":59,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The total heap size of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":60,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Heap Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each top_n executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":61,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"TopN Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in temporal join executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":62,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Cache Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in lookup executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":63,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lookup Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in over window executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":64,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_range_cache_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window partition range cache entry count | table {{table_id}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When enabled, this metric shows the input throughput of each executor.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":65,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_identity, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The actor-level memory usage statistics reported by TaskLocalAlloc. (Disabled by default)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":66,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(actor_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"actor_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Memory Usage (TaskLocalAlloc)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":6},"height":null,"hideTimeOverride":false,"id":67,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":68,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_actor_execution_time{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Execution Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":69,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":70,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":8},"height":null,"hideTimeOverride":false,"id":71,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":8},"height":null,"hideTimeOverride":false,"id":72,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":73,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":16},"height":null,"hideTimeOverride":false,"id":74,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":16},"height":null,"hideTimeOverride":false,"id":75,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":76,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":24},"height":null,"hideTimeOverride":false,"id":77,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":24},"height":null,"hideTimeOverride":false,"id":78,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":79,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":32},"height":null,"hideTimeOverride":false,"id":80,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":32},"height":null,"hideTimeOverride":false,"id":81,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":82,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":40},"height":null,"hideTimeOverride":false,"id":83,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Avg Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors (Tokio)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":7},"height":null,"hideTimeOverride":false,"id":84,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":85,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Send Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":86,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Recv Throughput","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Exchange","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":87,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during computation. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":88,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compute Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during source data ingestion. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":89,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during data sink out. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":90,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Errors by Type","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Streaming Errors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":9},"height":null,"hideTimeOverride":false,"id":91,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":92,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Exchange Recv Row Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":93,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_task_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mpp Task Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"All memory usage of batch executors in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":94,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"compute_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mem Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":95,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_heartbeat_worker_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Heartbeat Worker Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":96,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Row SeqScan Next Duration","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":97,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":98,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{table_id}} @ {{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Hummock has three parts of memory usage: 1. Meta Cache 2. Block CacheThis metric shows the real memory usage of each of these three caches.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":99,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"data cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":100,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='meta_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='data_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='data_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":101,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_scan_key_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type, table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{table_id}} @ {{type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iter keys flow","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":102,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p50 - {{table_id}} @ {{job}} @ {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p99 - {{table_id}} @ {{job}} @ {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts pmax - {{table_id}} @ {{job}} @ {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Merged SSTs","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the latency of Get operations that have been issued to the state store.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":103,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_get_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the time spent on iterator initialization.Histogram of the time spent on iterator scanning.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":104,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_init_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_iter_init_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_scan_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_iter_scan_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":105,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive count - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive count - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter check count- {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":106,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive rate - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"False-Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":107,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(((sum(rate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read req bloom filter false positive rate - {{table_id}} - {{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter False-Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":108,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_slow_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":109,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_shared_buffer_hit_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer hit - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_in_process_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":110,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":111,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":112,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p50 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p99 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read pmax - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Read Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":113,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Count - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of a single key-value pair when reading by operation Get.Operation Get gets a single key-value pair with respect to a caller-specified key. If the key does not exist in the storage, the size of key is counted into this metric and the size of value is 0.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":114,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance) + sum(rate(state_store_get_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of all the key-value paris when reading by operation Iter.Operation Iter scans a range of key-value pairs.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":115,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":116,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":117,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Read)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":11},"height":null,"hideTimeOverride":false,"id":118,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the real memory usage of uploader.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":119,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading memory - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task size - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader Memory Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of time spent on compacting shared buffer to remote storage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":120,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg Sync duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 upload task duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 upload task duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax upload task duration - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Build and Sync Sstable Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":121,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p50 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p99 - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write pmax - materialized view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Write Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":122,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_merge_imm_task_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"merge imm tasks - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_spill_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploader spill tasks - {{uploader_stage}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_syncing_epoch_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"syncing epoch count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Tasks Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":123,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_merge_imm_memory_sz{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Merging tasks memory size - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_spill_task_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploading tasks size - {{uploader_stage}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Task Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":124,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write batch - {{table_id}} @ {{job}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"l0 - {{job}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":125,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":126,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_write_batch_tuple_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write_batch_kv_pair_count - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Item Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":127,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_size_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) / sum(rate(state_store_write_batch_size_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_shared_buffer_to_sstable_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) / sum(rate(compactor_shared_buffer_to_sstable_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sync - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the statistics of mem_table size on flush. By default only max (p100) is shown.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":128,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_id, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_write_batch_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, table_id, job, instance) (rate(state_store_write_batch_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":129,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_mem_table_spill_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mem table spill table id - {{table_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Mem Table Spill Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":130,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Sync Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":131,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_event_handler_pending_event{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handler pending event number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":132,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 {{event_type}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 {{event_type}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax {{event_type}} {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax finished_task_wait_poll {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handle latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Write)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":12},"height":null,"hideTimeOverride":false,"id":133,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":134,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size(KB) of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":135,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Size(KB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The of bytes that have been written by commit epoch per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":136,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_commit_write_throughput{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{table_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Flush Bytes by Table","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":137,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result!='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Failure Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":138,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Success Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have been skipped.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":139,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_skip_compact_frequency{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (level, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{level}}-{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Skip Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg l0 select_level_count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":140,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_l0_compact_level_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_l0_compact_level_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task L0 Select Level Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg file count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":141,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_compact_task_file_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_compact_task_file_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task File Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The distribution of the compact task size triggered, including p90 and max. and categorize it according to different cg, levels and task types.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":142,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - cg{{group}}@{{type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task Size Distribution","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that are running.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":143,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_num{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_parallelism{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_pending_parallelism - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Running Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compact-task: The total time have been spent on compaction.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":144,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p50 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(compute_refill_cache_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compute_apply_version_duration_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(compactor_compact_task_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(compactor_compact_task_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(state_store_compact_sst_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(state_store_compact_sst_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"KBs read from next level during history compactions to next level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":145,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job) + sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_fast_compact_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fast compact - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Write refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":146,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Bytes(GiB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Write amplification is the amount of bytes written to the remote storage by compaction for each one byte of flushed SSTable data. Write amplification is by definition higher than 1.0 because we write each piece of data to L0, and then write it again to an SSTable, and then compaction may read this piece of data and write it to a new SSTable, that's another write.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":147,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) / sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"})","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write amplification","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Amplification","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables that is being compacted at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":148,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"num of compact_task","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":149,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_task_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":150,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"KBs Read/Write by Level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":151,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_write_sstn{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Count of SSTs Read/Write by level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_bloom_filter, for observing bloom_filter size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":152,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_meta - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_file_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_file_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_file - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_avg_key_size, for observing sstable_avg_key_size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":153,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_key_size - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_value_size - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Item Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg count gotten from sstable_distinct_epoch_count, for observing sstable_distinct_epoch_count","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":154,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_epoch_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Stat","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total time of operations which read from remote storage when enable prefetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":155,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Remote Read Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":156,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_iter_scan_key_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{type}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Iter keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"bytes of Lsm tree needed to reach balance","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":157,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_pending_bytes{job=~\"$job\",instance=~\"$node\"}) by (instance, group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact pending bytes - {{group}} @ {{instance}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Compact Pending Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compression ratio of each level of the lsm tree","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":158,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_level_compression_ratio{job=~\"$job\",instance=~\"$node\"}) by (instance, group, level, algorithm)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lsm compression ratio - cg{{group}} @ L{{level}} - {{algorithm}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Level Compression Ratio","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Compaction","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":13},"height":null,"hideTimeOverride":false,"id":159,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":160,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":161,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, type, job, instance)(rate(object_store_operation_latency_sum{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(object_store_operation_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} avg - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":162,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type!~'streaming_upload_write_bytes|streaming_read_read_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'upload|delete',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-write - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'read|readv|list|metadata',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-read - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":163,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":164,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Failure Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":165,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(aws_sdk_retry_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(s3_read_request_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Retry Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"There are two types of operations: 1. GET, SELECT, and DELETE, they cost 0.0004 USD per 1000 requests. 2. PUT, COPY, POST, LIST, they cost 0.005 USD per 1000 requests.Reading from S3 across different regions impose extra cost. This metric assumes 0.01 USD per 1GB data transfer. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":166,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}) * 0.01 / 1000 / 1000 / 1000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"(Cross Region) Data Transfer Cost","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'read|streaming_read_start|delete',job=~\"$job\",instance=~\"$node\"}) * 0.0004 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GET, SELECT, and all other Requests Cost","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|delete_objects|list',job=~\"$job\",instance=~\"$node\"}) * 0.005 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"PUT, COPY, POST, LIST Requests Cost","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Realtime)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric uses the total size of data in S3 at this second to derive the cost of storing data for a whole month. The price is 0.023 USD per GB. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":167,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance) * 0.023 / 1000 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Monthly Storage Cost","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Monthly)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Object Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":14},"height":null,"hideTimeOverride":false,"id":168,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":169,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, extra, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":170,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":171,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, extra, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":172,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_duration_count{op=\"lookup\",extra=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, instance) / (sum(rate(foyer_storage_op_duration_count{op=\"lookup\",extra=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, instance) + sum(rate(foyer_storage_op_duration_count{op=\"lookup\",extra=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache hit ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":173,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=~\"meta|data\",op!~\"filtered|ignored\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":174,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Data Refill Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":175,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":176,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(refill_queue_total) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"refill queue length @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Queue Length","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":177,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(foyer_storage_total_bytes{job=~\"$job\",instance=~\"$node\"}) by (foyer, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} size @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":178,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inner Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":179,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_slow_op_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, extra, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{foyer}} file cache {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":180,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_slow_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, extra, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} file cache - {{op}} {{extra}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":181,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":182,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"parent_meta\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup hit ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":183,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":184,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"unit_inheritance\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":185,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":186,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",op=\"success\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / sum(rate(refill_total{type=\"block\",op=\"unfiltered\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill ratio @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ratio","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Tiered Cache","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":15},"height":null,"hideTimeOverride":false,"id":187,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":188,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p50 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p99 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time pmax - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lock Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":189,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p50 - {{method}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p99 - {{method}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time pmax - {{method}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Real Process Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":190,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version size","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":191,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"current version id","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"checkpoint version id","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned version id","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_safepoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min safepoint version id","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Id","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":192,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"max committed epoch","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_safe_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"safe epoch","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned epoch","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":193,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_value_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":194,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":195,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_count',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table KV Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\nObjects are classified into 3 groups:\n- not referenced by versions: these object are being deleted from object store.\n- referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n- referenced by current version: these objects are in the latest version.\n\nAdditionally, a metric on all objects (including dangling ones) is updated with low-frequency. The metric is updated right before full GC. So subsequent full GC may reduce the actual value significantly, without updating the metric.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":196,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects (including dangling ones)","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Refer to `Object Total Number` panel for classification of objects.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":197,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects, including dangling ones","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of hummock version delta log","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":198,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_delta_log_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"delta log total number","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Delta Log Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"hummock version checkpoint latency","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":199,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p999","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_pmax","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(storage_version_checkpoint_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(storage_version_checkpoint_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Checkpoint Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When certain per compaction group threshold is exceeded (e.g. number of level 0 sub-level in LSMtree), write op to that compaction group is stopped temporarily. Check log for detail reason of write stop.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":200,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compaction_group_{{compaction_group_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Stop Compaction Groups","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of attempts to trigger full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":201,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_trigger_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_trigger_count","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Trigger Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"the object id watermark used in last full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":202,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_last_object_id_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_last_object_id_watermark","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Last Watermark","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":203,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency pmax - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Event Loop Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The times of move_state_table occurs","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":204,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_move_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"move table cg{{group}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Move State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of state_tables in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":205,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"state table cg{{group}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of branched_sst in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":206,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_branched_sst_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"branched sst cg{{group}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Branched SST Count","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":207,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total backup job count since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":208,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"backup_job_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"job count","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Latency of backup jobs since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":209,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p50 - {{state}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p99 - {{state}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time pmax - {{state}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Process Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Backup Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":17},"height":null,"hideTimeOverride":false,"id":210,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":211,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":212,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Drop latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":213,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetCatalog latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Catalog Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":214,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":215,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"AddWorkerNode latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":216,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ListAllNodes latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Cluster Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":19},"height":null,"hideTimeOverride":false,"id":217,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":218,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CreateMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":219,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"DropMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":220,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Flush latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Stream Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":20},"height":null,"hideTimeOverride":false,"id":221,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":222,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinVersionBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":223,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinSnapshotBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":224,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ReportCompactionTasks latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":225,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p90","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetNewSstIds latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":21},"height":null,"hideTimeOverride":false,"id":226,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":227,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_counts - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":228,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_version_before_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_version_before_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"version_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":229,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latencyp90 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":230,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_counts - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_counts - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":231,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_get_new_sst_ids_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":232,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_counts - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":233,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_avg","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC: Hummock Meta Client","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":22},"height":null,"hideTimeOverride":false,"id":234,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":235,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":236,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":237,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":238,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Running Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":239,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Rejected queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":240,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Completed Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":241,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":242,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Frontend","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":23},"height":null,"hideTimeOverride":false,"id":243,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":244,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(lru_runtime_loop_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager loop count per sec","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":245,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_watermark_step{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager watermark steps","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"watermark_time is the current lower watermark of cached data. physical_now is the current time of the machine. The diff (physical_now - watermark_time) shows how much data is cached.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":246,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_physical_now_ms{job=~\"$job\",instance=~\"$node\"} - lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager diff between watermark_time and now (ms)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":247,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":248,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":249,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_resident_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The resident memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":250,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_metadata_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The metadata memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":251,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":252,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":253,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"} - on() group_right() lru_evicted_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager diff between current watermark and evicted watermark time (ms) for actors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":254,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":255,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":256,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":25},"height":null,"hideTimeOverride":false,"id":257,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":258,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, connector, sink_id)(rate(sink_commit_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(sink_commit_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":259,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest write epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest read epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Read/Write Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":260,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(max(log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":261,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min((max(log_store_first_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume persistent log lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Persistent Log Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":262,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":263,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":264,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":265,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":266,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":267,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":268,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":269,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":270,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_rewind_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Rewind Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":271,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(kv_log_store_rewind_delay_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor_id, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rewind delay (second)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":272,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka high watermark by source and partition and source latest message by partition, source and actor","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":273,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"high watermark: source={{source_id}} partition={{partition}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_latest_message_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest msg: source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka high watermark and source latest message","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current number of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":274,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current total size of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":275,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Size in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages transmitted (produced) to Kafka brokers","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":276,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_tx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Produced Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages consumed, not including ignored messages (due to offset, etc), from Kafka brokers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":277,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_rx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Received Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages awaiting transmission to broker","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":278,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_outbuf_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count Pending to Transmit (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages in-flight to broker awaiting response","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":279,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_waitresp_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inflight Message Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of transmission errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":280,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_tx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Transmitting (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of receive errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":281,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Receiving (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of requests timed out","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":282,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_req_timeouts{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Timeout Request Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker latency / round-trip time in milli seconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":283,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"RTT (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker throttling time in milliseconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":284,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Throttle Time (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Age of metadata from broker for this topic (milliseconds)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":285,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_metadata_age{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Metadata_age Age","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch sizes in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":286,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch message counts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":null,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Messages","transformations":[],"transparent":false,"type":"timeseries"}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages ready to be produced in transmit queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":287,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_xmit_msgq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message to be Transmitted","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of pre-fetched messages in fetch queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":288,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_fetchq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message in pre fetch queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Next offset to fetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":289,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_next_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Next offset to fetch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Last committed offset","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":290,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_committed_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Committed Offset","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Kafka Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":27},"height":null,"hideTimeOverride":false,"id":291,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":292,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Network throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":293,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"S3 throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":294,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} write @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total read @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total write @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"gRPC throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":295,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_io_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} grpc {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"IO error rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":296,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Existing connection count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":297,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":298,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection err rate","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network connection","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":28},"height":null,"hideTimeOverride":false,"id":299,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"iceberg write qps","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":300,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_write_qps{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Qps Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":301,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{sink_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, sink_id)(rate(iceberg_write_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(iceberg_write_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Latency Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":302,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_rolling_unfushed_data_file{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg rolling unfushed data file","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":303,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_position_delete_cache_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg position delete cache num","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":304,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_partition_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg partition num","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Iceberg Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":29},"height":null,"hideTimeOverride":false,"id":305,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":306,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Calls Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":307,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_input_chunk_rows_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_input_chunk_rows_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_input_chunk_rows_avg - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Input Chunk Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":308,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.50, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p50 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p90 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99 - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, link, name, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":309,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":310,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{link}} {{name}} {{fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (bytes)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Defined Function","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(table_info, table_id)","description":"Reporting table id of the metric","hide":0,"includeAll":true,"label":"Table","multi":true,"name":"table","options":[],"query":{"query":"label_values(table_info, table_id)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dev_dashboard","uid":"Ecy3uV1nz","version":0} +{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dev Dashboard","editable":true,"gnetId":null,"graphTooltip":0,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Actor count per compute node","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Count (Group By Compute Node)","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":5,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","query":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Memory usage relative to k8s resource limit of container. Only works in K8s environment","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(avg by(namespace, pod) (container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\"})) / ( sum by(namespace, pod) (kube_pod_container_resource_limits{namespace=~\"$namespace\", pod=~\"$pod\", container=\"$component\", resource=\"memory\", unit=\"byte\"}))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg memory usage @ {{job}} @ {{instance}}","metric":"","query":"(avg by(namespace, pod) (container_memory_working_set_bytes{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\"})) / ( sum by(namespace, pod) (kube_pod_container_resource_limits{namespace=~\"$namespace\", pod=~\"$pod\", container=\"$component\", resource=\"memory\", unit=\"byte\"}))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory relative","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":9,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (total) - {{job}} @ {{instance}}","metric":"","query":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (job, instance) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage (avg per core) - {{job}} @ {{instance}}","metric":"","query":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (job, instance) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"CPU usage relative to k8s resource limit of container. Only works in K8s environment","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\",container=~\"$component\",pod=~\"$pod\"}[$__rate_interval])) by (namespace, pod)) / (sum(kube_pod_container_resource_limits{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\", resource=\"cpu\"}) by (namespace, pod))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cpu usage @ {{job}} @ {{instance}}","metric":"","query":"(sum(rate(container_cpu_usage_seconds_total{namespace=~\"$namespace\",container=~\"$component\",pod=~\"$pod\"}[$__rate_interval])) by (namespace, pod)) / (sum(kube_pod_container_resource_limits{namespace=~\"$namespace\",pod=~\"$pod\",container=~\"$component\", resource=\"cpu\"}) by (namespace, pod))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU relative","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"RW cluster can configure multiple meta nodes to achieve high availability. One is the leader and the rest are the followers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(meta_num{job=~\"$job\",instance=~\"$node\"}) by (worker_addr,role)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_addr}} @ {{role}}","metric":"","query":"sum(meta_num{job=~\"$job\",instance=~\"$node\"}) by (worker_addr,role)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Meta Cluster","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Cluster Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":12,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The rate of successful recovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":13,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery Successful Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of failed reocovery attempts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Failed recovery attempts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Time spent in a successful recovery attempt","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":15,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency pmax - {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(recovery_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by (le) (rate(recovery_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by (le) (rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recovery latency avg","metric":"","query":"sum by (le) (rate(recovery_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by (le) (rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recovery latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Recovery","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":3},"height":null,"hideTimeOverride":false,"id":16,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of barriers that have been ingested but not completely processed. This metric reflects the current level of congestion within the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all_barrier","metric":"","query":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"in_flight_barrier_nums{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"in_flight_barrier","metric":"","query":"in_flight_barrier_nums{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":18,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","query":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration from the last committed barrier's epoch time to the current time. This metric reflects the data freshness of the system. During this time, no new data has been committed.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":19,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"timestamp(last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}) - last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_pending_time","metric":"","query":"timestamp(last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}) - last_committed_barrier_time{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier pending time (secs)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(rows).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_partition_input_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragment_id={{fragment_id}}","metric":"","query":"rate(source_partition_input_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Each query is executed in parallel with a user-defined parallelism. This figure shows the throughput of each parallelism. The throughput of all the parallelism added up is equal to Source Throughput(MB/s).","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor={{actor_id}} source={{source_id}} partition={{partition}} fragment_id={{fragment_id}}","metric":"","query":"(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s) Per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Monitor each source upstream, 0 means the upstream is not normal, 1 means the source is ready.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","query":"source_status_is_up{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Upstream Status","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Source Split Change Events frequency by source_id and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_split_change_event_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_source_split_change_event_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Split Change Events frequency(events/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka Consumer Lag Size by source_id, partition and actor_id","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":27,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min(source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"} - on(source_id, partition) group_right() source_latest_message_id{job=~\"$job\",instance=~\"$node\"}, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","query":"clamp_min(source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"} - on(source_id, partition) group_right() source_latest_message_id{job=~\"$job\",instance=~\"$node\"}, 0)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka Consumer Lag Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":28,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","query":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}} - actor {{actor_id}}","metric":"","query":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","query":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":31,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(fragment_id, table_id) group_left(table_name) table_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}} - actor {{actor_id}} fragment_id {{fragment_id}}","metric":"","query":"rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(fragment_id, table_id) group_left(table_name) table_info{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s) per Partition","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":32,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The duration between the time point when the scheduled barrier needs to be sent and the time point when the barrier gets actually sent to all the compute nodes. Developers can thus detect any internal congestion.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_barrier_send_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_send_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_send_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_send_latency_avg","metric":"","query":"rate(meta_barrier_send_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_send_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Send Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_barrier_inflight_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"max(sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_inflight_latency_avg","metric":"","query":"max(sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_inflight_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier In-Flight Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":36,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_p999 - {{instance}}","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_pmax - {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_barrier_sync_storage_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_sync_latency_avg - {{instance}}","metric":"","query":"sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, instance)(rate(stream_barrier_sync_storage_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Sync Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_barrier_wait_commit_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_wait_commit_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_wait_commit_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_wait_commit_avg","metric":"","query":"rate(meta_barrier_wait_commit_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_wait_commit_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Wait Commit Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of actors that have processed the earliest in-flight barriers per second. This metric helps users to detect potential congestion or stuck in the system.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":38,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_barrier_manager_progress{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"rate(stream_barrier_manager_progress{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Earliest In-Flight Barrier Progress","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":4},"height":null,"hideTimeOverride":false,"id":39,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the cdc backfill snapshot","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_cdc_backfill_snapshot_read_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Snapshot Read Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been output from the cdc backfill upstream","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":41,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_cdc_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_cdc_backfill_upstream_output_row_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Backfill Upstream Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p50 - {{table_name}}","metric":"","query":"histogram_quantile(0.5, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag p99 - {{table_name}}","metric":"","query":"histogram_quantile(0.99, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lag pmax - {{table_name}}","metric":"","query":"histogram_quantile(1.0, sum(rate(source_cdc_event_lag_duration_milliseconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_name))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Consume Lag Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(cdc_source_error{job=~\"$job\",instance=~\"$node\"}) by (connector_name, source_id, error_msg)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{connector_name}}: {{error_msg}} ({{source_id}})","metric":"","query":"sum(cdc_source_error{job=~\"$job\",instance=~\"$node\"}) by (connector_name, source_id, error_msg)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CDC Source Errors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming CDC","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":5},"height":null,"hideTimeOverride":false,"id":44,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":45,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","query":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":46,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_input_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","query":"avg(rate(stream_actor_input_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}<-{{upstream_fragment_id}}","metric":"","query":"sum(rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, upstream_fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"rate(stream_actor_in_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Input Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"rate(stream_actor_out_record_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Throughput (rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The operator-level memory usage statistics collected by each LRU cache","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (table_id, desc)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} desc: {{desc}}","metric":"","query":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (table_id, desc)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","query":"stream_memory_usage{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Memory usage aggregated by materialized views","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{materialized_view_id}}","metric":"","query":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Memory Usage of Materialized Views","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":51,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"temporal join cache miss, table_id {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":52,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache hit count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_materialize_cache_hit_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total cached count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_materialize_cache_total_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialize Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache lookup count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_over_window_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cache miss count - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_over_window_cache_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache lookup count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_range_cache_lookup_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_left_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache left miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_range_cache_left_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_over_window_range_cache_right_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"partition range cache right miss count - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_over_window_range_cache_right_miss_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":54,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} fragment {{fragment_id}}","metric":"","query":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize executor cache miss ratio - table {{table_id}} fragment {{fragment_id}} {{instance}}","metric":"","query":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window cache miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_over_window_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_left_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache left miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_over_window_range_cache_left_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_over_window_range_cache_right_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Over window partition range cache right miss ratio - table {{table_id}} fragment {{fragment_id}} ","metric":"","query":"(sum(rate(stream_over_window_range_cache_right_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id) ) / (sum(rate(stream_over_window_range_cache_lookup_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":55,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p999 - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor, fragment_id, wait_side, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, executor, fragment_id, wait_side, job)(rate(stream_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,executor,fragment_id,wait_side,job) (rate(stream_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - executor {{executor}} fragment {{fragment_id}} {{wait_side}} - {{job}}","metric":"","query":"sum by(le, executor, fragment_id, wait_side, job)(rate(stream_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,executor,fragment_id,wait_side,job) (rate(stream_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Barrier Align","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":56,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p999 - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(0.999, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_merge_barrier_align_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, fragment_id, job)(rate(stream_merge_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,fragment_id,job) (rate(stream_merge_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} - {{job}}","metric":"","query":"sum by(le, fragment_id, job)(rate(stream_merge_barrier_align_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le,fragment_id,job) (rate(stream_merge_barrier_align_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Merger Barrier Align","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":57,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","query":"avg(rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"rate(stream_join_actor_input_waiting_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Input Blocking Time Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":58,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id,side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","query":"avg(rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000) by (fragment_id,side)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","query":"rate(stream_join_match_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Actor Match Duration Per Second","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Multiple rows with distinct primary keys may have the same join key. This metric counts the number of join keys in the executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":59,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (fragment_id, side)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}} {{side}}","metric":"","query":"sum(stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (fragment_id, side)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}} {{side}}","metric":"","query":"stream_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of matched rows on the opposite side","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":60,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(stream_join_matched_join_keys_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, fragment_id, table_id, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - fragment {{fragment_id}} table_id {{table_id}} - {{job}}","metric":"","query":"sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, fragment_id, table_id) (rate(stream_join_matched_join_keys_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Join Executor Matched Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":61,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level cache miss - table {{table_id}} actor {{actor_id}}}","metric":"","query":"rate(stream_agg_chunk_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"chunk-level total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_chunk_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Executor Cache Statistics For Each StreamChunk","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":62,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg distinct cached keys count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_distinct_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":63,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_dirty_groups_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The total heap size of dirty (unflushed) groups in each hash aggregation executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":64,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"stream agg dirty groups heap size | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_agg_dirty_groups_heap_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Aggregation Dirty Groups Heap Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in each top_n executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":65,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_group_top_n_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"group top_n appendonly cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_group_top_n_appendonly_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"TopN Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in temporal join executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":66,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal Join cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_temporal_join_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Temporal Join Cache Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in lookup executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":67,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lookup cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_lookup_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lookup Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of keys cached in over window executor's executor cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":68,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window cached count | table {{table_id}} actor {{actor_id}}","metric":"","query":"stream_over_window_cached_entry_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_over_window_range_cache_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"over window partition range cache entry count | table {{table_id}} fragment {{fragment_id}}","metric":"","query":"sum(stream_over_window_range_cache_entry_count{job=~\"$job\",instance=~\"$node\"}) by (table_id, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Over Window Cached Keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When enabled, this metric shows the input throughput of each executor.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":69,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_identity, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} fragment {{fragment_id}}","metric":"","query":"sum(rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_identity, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_identity}} actor {{actor_id}}","metric":"","query":"rate(stream_executor_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The actor-level memory usage statistics reported by TaskLocalAlloc. (Disabled by default)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":96},"height":null,"hideTimeOverride":false,"id":70,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(actor_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}","metric":"","query":"sum(actor_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"actor_memory_usage{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"actor {{actor_id}}","metric":"","query":"actor_memory_usage{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Memory Usage (TaskLocalAlloc)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":6},"height":null,"hideTimeOverride":false,"id":71,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":72,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_actor_execution_time{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_actor_execution_time{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Execution Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":73,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":74,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":8},"height":null,"hideTimeOverride":false,"id":75,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_fast_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_fast_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Fast Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":8},"height":null,"hideTimeOverride":false,"id":76,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":77,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":16},"height":null,"hideTimeOverride":false,"id":78,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_slow_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_slow_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Slow Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":16},"height":null,"hideTimeOverride":false,"id":79,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":80,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":24},"height":null,"hideTimeOverride":false,"id":81,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_poll_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_poll_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Poll Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":24},"height":null,"hideTimeOverride":false,"id":82,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":83,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":32},"height":null,"hideTimeOverride":false,"id":84,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_idle_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_idle_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Idle Avg Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":32},"height":null,"hideTimeOverride":false,"id":85,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Total Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":86,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":40},"height":null,"hideTimeOverride":false,"id":87,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{actor_id}}","metric":"","query":"rate(stream_actor_scheduled_duration{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(stream_actor_scheduled_cnt{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Tokio: Actor Scheduled Avg Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Actors (Tokio)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":7},"height":null,"hideTimeOverride":false,"id":88,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":89,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","query":"rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Send Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":90,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{up_fragment_id}}->{{down_fragment_id}}","metric":"","query":"rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fragment-level Remote Exchange Recv Throughput","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming Exchange","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":91,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during computation. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":92,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","query":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compute Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during source data ingestion. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":93,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Errors by Type","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors that happened during data sink out. Check the logs for detailed error message.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":94,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Errors by Type","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Streaming Errors","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":9},"height":null,"hideTimeOverride":false,"id":95,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":96,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","query":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Exchange Recv Row Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":97,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_task_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"batch_task_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mpp Task Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"All memory usage of batch executors in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":98,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"compute_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"compute_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"frontend_batch_total_mem{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Mem Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":99,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_heartbeat_worker_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"batch_heartbeat_worker_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Heartbeat Worker Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":100,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(batch_row_seq_scan_next_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"row_seq_scan next avg - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(batch_row_seq_scan_next_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Row SeqScan Next Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Disk throughputs of spilling-out in the bacth query engine","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":101,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(batch_spill_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}} @ {{instance}}","metric":"","query":"sum(rate(batch_spill_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(batch_spill_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}} @ {{instance}}","metric":"","query":"sum(rate(batch_spill_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Spill Throughput","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":102,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":103,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{table_id}} @ {{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Hummock has three parts of memory usage: 1. Meta Cache 2. Block CacheThis metric shows the real memory usage of each of these three caches.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":104,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache - {{job}} @ {{instance}}","metric":"","query":"avg(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"data cache - {{job}} @ {{instance}}","metric":"","query":"avg(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(state_store_prefetch_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"prefetch cache - {{job}} @ {{instance}}","metric":"","query":"avg(state_store_prefetch_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":105,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='meta_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"(sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='meta_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_sst_store_block_request_counts{type='data_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='data_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache miss ratio - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"(sum(rate(state_store_sst_store_block_request_counts{type='data_miss',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) / (sum(rate(state_store_sst_store_block_request_counts{type='data_total',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the estimated hit ratio of a block while in the block cache.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":106,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.1, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p10 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.1, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.25, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p25 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.25, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.5, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p50 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.5, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.75, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p75 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.75, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(0.9, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p90 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(0.9, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_max(histogram_quantile(1.0, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block cache efficienfy - p100 - {{job}} @ {{instance}}","metric":"","query":"clamp_max(histogram_quantile(1.0, sum(rate(block_efficiency_histogram_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le,job,instance)), 1)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Cache Efficiency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":107,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_scan_key_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type, table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{table_id}} @ {{type}} @ {{instance}}","metric":"","query":"sum(rate(state_store_iter_scan_key_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type, table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iter keys flow","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":108,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p50 - {{table_id}} @ {{job}} @ {{type}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts p99 - {{table_id}} @ {{job}} @ {{type}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts pmax - {{table_id}} @ {{job}} @ {{type}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_merge_sstable_counts_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, table_id, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"# merged ssts avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_iter_merge_sstable_counts_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Merged SSTs","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the latency of Get operations that have been issued to the state store.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":109,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_get_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_get_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id)(rate(state_store_get_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of the time spent on iterator initialization.Histogram of the time spent on iterator scanning.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":110,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_init_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_init_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_init_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"create_iter_time avg - {{iter_type}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(state_store_iter_init_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_init_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_scan_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_iter_scan_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_scan_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pure_scan_time avg - {{iter_type}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(state_store_iter_scan_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, iter_type) (rate(state_store_iter_scan_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Duration - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":111,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive count - {{table_id}} - {{type}}","metric":"","query":"sum(irate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive count - {{table_id}} - {{type}}","metric":"","query":"sum(irate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter check count- {{table_id}} - {{type}}","metric":"","query":"sum(irate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":112,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter positive rate - {{table_id}} - {{type}}","metric":"","query":"(sum(rate(state_store_read_req_bloom_filter_positive_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"False-Positive / Total","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":113,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(((sum(rate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read req bloom filter false positive rate - {{table_id}} - {{type}}","metric":"","query":"(((sum(rate(state_store_read_req_positive_but_non_exist_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) / (sum(rate(state_store_read_req_check_bloom_filter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id,type))) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Bloom Filter False-Positive Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":114,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_slow_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"state_store_iter_slow_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Slow Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":115,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_get_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_shared_buffer_hit_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer hit - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_get_shared_buffer_hit_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id, iter_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{iter_type}} - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_iter_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id, iter_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":116,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":117,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Size - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":118,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p50 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.5, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.5, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.5, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read p99 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.99, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(0.99, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(0.99, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read pmax - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(1.0, sum(rate(state_store_iter_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id) + sum((histogram_quantile(1.0, sum(rate(state_store_get_key_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) + histogram_quantile(1.0, sum(rate(state_store_get_value_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Read Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":119,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{iter_type}} {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_item_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id, iter_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_in_progress_counts{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Existing {{iter_type}} count @ {{table_id}}","metric":"","query":"state_store_iter_in_progress_counts{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_log_op_type_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, op_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter_log op count @ {{table_id}} {{op_type}}","metric":"","query":"sum(rate(state_store_iter_log_op_type_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, op_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Item Count - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of a single key-value pair when reading by operation Get.Operation Get gets a single key-value pair with respect to a caller-specified key. If the key does not exist in the storage, the size of key is counted into this metric and the size of value is 0.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":120,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_get_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance) + sum(rate(state_store_get_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_get_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance) + sum(rate(state_store_get_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Get","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size of all the key-value paris when reading by operation Iter.Operation Iter scans a range of key-value pairs.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":121,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_iter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_iter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Read Throughput - Iter","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":122,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_iter_fetch_meta_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fetch_meta_duration avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id) (rate(state_store_iter_fetch_meta_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":123,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_iter_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"state_store_iter_fetch_meta_cache_unhits{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Fetch Meta Unhits","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Read)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":11},"height":null,"hideTimeOverride":false,"id":124,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the real memory usage of uploader.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":125,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading memory - {{job}} @ {{instance}}","metric":"","query":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploader imm size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unflushed imm size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_uploading_task_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"orphan imm size - {{job}} @ {{instance}}","metric":"","query":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance) - sum(state_store_uploader_imm_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_old_value_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"old value size - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_old_value_size{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader Memory Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Histogram of time spent on compacting shared buffer to remote storage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":126,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 Sync duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 Sync duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax Sync duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_sync_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg Sync duration - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(state_store_sync_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 upload task duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 upload task duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax upload task duration - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_uploader_upload_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Build and Sync Sstable Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":127,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.5, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p50 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.5, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(0.99, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write p99 - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(0.99, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write pmax - materialized view {{materialized_view_id}}","metric":"","query":"sum(histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id)) * on(table_id) group_left(materialized_view_id) (group(table_info{job=~\"$job\",instance=~\"$node\"}) by (materialized_view_id, table_id))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Write Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":128,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_merge_imm_task_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"merge imm tasks - {{table_id}} @ {{instance}}","metric":"","query":"sum(irate(state_store_merge_imm_task_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_spill_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploader spill tasks - {{uploader_stage}} @ {{instance}}","metric":"","query":"sum(irate(state_store_spill_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_uploading_task_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"uploading task count - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_uploading_task_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_uploader_syncing_epoch_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"syncing epoch count - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_uploader_syncing_epoch_count{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Tasks Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":129,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_merge_imm_memory_sz{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Merging tasks memory size - {{table_id}} @ {{instance}}","metric":"","query":"sum(rate(state_store_merge_imm_memory_sz{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_spill_task_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Uploading tasks size - {{uploader_stage}} @ {{instance}}","metric":"","query":"sum(rate(state_store_spill_task_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,uploader_stage)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Uploader - Task Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":130,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write batch - {{table_id}} @ {{job}} @ {{instance}} ","metric":"","query":"sum(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"l0 - {{job}} @ {{instance}} ","metric":"","query":"sum(rate(state_store_sync_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":131,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p50 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer p99 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_write_batch_duration_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to shared_buffer avg - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance, table_id)(rate(state_store_write_batch_duration_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_write_shared_buffer_sync_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write to object_store - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(state_store_write_shared_buffer_sync_time_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":132,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_write_batch_tuple_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write_batch_kv_pair_count - {{table_id}} @ {{instance}}","metric":"","query":"sum(irate(state_store_write_batch_tuple_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Item Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":133,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_write_batch_size_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) / sum(rate(state_store_write_batch_size_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"shared_buffer - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_write_batch_size_sum{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) / sum(rate(state_store_write_batch_size_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job,instance,table_id) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_shared_buffer_to_sstable_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) / sum(rate(compactor_shared_buffer_to_sstable_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sync - {{job}} @ {{instance}}","metric":"","query":"sum(rate(compactor_shared_buffer_to_sstable_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) / sum(rate(compactor_shared_buffer_to_sstable_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric shows the statistics of mem_table size on flush. By default only max (p100) is shown.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":134,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_id, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_write_batch_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, table_id, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_write_batch_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, table_id, job, instance) (rate(state_store_write_batch_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{table_id}} {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(state_store_write_batch_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, table_id, job, instance) (rate(state_store_write_batch_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":135,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_mem_table_spill_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mem table spill table id - {{table_id}} @ {{instance}}","metric":"","query":"sum(irate(state_store_mem_table_spill_counts{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Mem Table Spill Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":136,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Sync Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":137,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_event_handler_pending_event{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"sum(state_store_event_handler_pending_event{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handler pending event number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":138,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 {{event_type}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 {{event_type}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax {{event_type}} {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_event_handler_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, event_type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 finished_task_wait_poll {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax finished_task_wait_poll {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_uploader_wait_poll_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Event handle latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock (Write)","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":12},"height":null,"hideTimeOverride":false,"id":139,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":140,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","query":"sum(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The size(KB) of SSTables at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":141,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","query":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"SSTable Size(KB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The of bytes that have been written by commit epoch per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":142,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_commit_write_throughput{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{table_id}}","metric":"","query":"sum(rate(storage_commit_write_throughput{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Flush Bytes by Table","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":143,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result!='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","query":"sum(storage_level_compact_frequency{result!='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Failure Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have completed or failed","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":144,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_frequency{result='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task_type}} - {{result}} - group-{{group}} @ {{compactor}}","metric":"","query":"sum(storage_level_compact_frequency{result='SUCCESS',job=~\"$job\",instance=~\"$node\"}) by (compactor, group, task_type, result)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Success Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that have been skipped.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":145,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_skip_compact_frequency{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (level, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{level}}-{{type}}","metric":"","query":"sum(rate(storage_skip_compact_frequency{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (level, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Skip Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg l0 select_level_count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":146,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_l0_compact_level_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_l0_compact_level_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","query":"sum by(le, group, type)(irate(storage_l0_compact_level_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_l0_compact_level_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task L0 Select Level Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg file count of the compact task, and categorize it according to different cg, levels and task types","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":147,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, group, type)(irate(storage_compact_task_file_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_compact_task_file_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg cg{{group}}@{{type}}","metric":"","query":"sum by(le, group, type)(irate(storage_compact_task_file_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, group, type)(irate(storage_compact_task_file_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task File Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The distribution of the compact task size triggered, including p90 and max. and categorize it according to different cg, levels and task types.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":148,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - cg{{group}}@{{type}}","metric":"","query":"histogram_quantile(0.9, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - cg{{group}}@{{type}}","metric":"","query":"histogram_quantile(1.0, sum(rate(storage_compact_task_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, type))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Task Size Distribution","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of compactions from one level to another level that are running.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":149,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_num{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_count - {{job}} @ {{instance}}","metric":"","query":"avg(storage_compact_task_pending_num{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(storage_compact_task_pending_parallelism{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor_task_pending_parallelism - {{job}} @ {{instance}}","metric":"","query":"avg(storage_compact_task_pending_parallelism{job=~\"$job\",instance=~\"$node\"}) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Running Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compact-task: The total time have been spent on compaction.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":150,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p50 - {{job}}","metric":"","query":"histogram_quantile(0.5, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compact_task_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compact_sst_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get-table-id pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(compactor_get_table_id_total_time_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{job}}","metric":"","query":"histogram_quantile(0.9, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{job}}","metric":"","query":"histogram_quantile(1.0, sum(rate(compactor_remote_read_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(compute_refill_cache_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compute_apply_version_duration_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(compute_refill_cache_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(compactor_compact_task_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(compactor_compact_task_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-task avg","metric":"","query":"sum by(le)(rate(compactor_compact_task_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(compactor_compact_task_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le)(rate(state_store_compact_sst_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(state_store_compact_sst_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact-key-range avg","metric":"","query":"sum by(le)(rate(state_store_compact_sst_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le)(rate(state_store_compact_sst_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"KBs read from next level during history compactions to next level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":151,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job) + sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","query":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job) + sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","query":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","query":"sum(rate(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_fast_compact_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fast compact - {{job}}","metric":"","query":"sum(rate(compactor_fast_compact_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by (job)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Write refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":152,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","query":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"flush - {{job}}","metric":"","query":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Bytes(GiB)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Write amplification is the amount of bytes written to the remote storage by compaction for each one byte of flushed SSTable data. Write amplification is by definition higher than 1.0 because we write each piece of data to L0, and then write it again to an SSTable, and then compaction may read this piece of data and write it to a new SSTable, that's another write.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":153,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) / sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write amplification","metric":"","query":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) / sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Write Amplification","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of SSTables that is being compacted at each level","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":154,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"L{{level_index}}","metric":"","query":"storage_level_compact_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting SSTable Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"num of compact_task","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":155,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_level_compact_task_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{task}}","metric":"","query":"storage_level_compact_task_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compacting Task Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":156,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","query":"sum(rate(storage_level_compact_read_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","query":"sum(rate(storage_level_compact_read_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","query":"sum(rate(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"KBs Read/Write by Level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":157,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_write_sstn{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} write to next level","metric":"","query":"sum(irate(storage_level_compact_write_sstn{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from next level","metric":"","query":"sum(irate(storage_level_compact_read_sstn_next{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_level_compact_read_sstn_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"cg{{group}}-L{{level_index}} read from current level","metric":"","query":"sum(irate(storage_level_compact_read_sstn_curr{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, group, level_index)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Count of SSTs Read/Write by level","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_bloom_filter, for observing bloom_filter size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":158,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_meta - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_bloom_filter_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_file_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_file_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_file - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_file_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_file_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total bytes gotten from sstable_avg_key_size, for observing sstable_avg_key_size","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":159,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_key_size - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_key_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_value_size - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_avg_value_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Item Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Avg count gotten from sstable_distinct_epoch_count, for observing sstable_distinct_epoch_count","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":160,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg_epoch_count - {{job}} @ {{instance}}","metric":"","query":"sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job, instance)(rate(compactor_sstable_distinct_epoch_count_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Sstable Stat","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total time of operations which read from remote storage when enable prefetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":161,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io p90 - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote-io pmax - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(state_store_remote_read_time_per_task_bucket{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance, table_id))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hummock Remote Read Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":162,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(compactor_iter_scan_key_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"iter keys flow - {{type}} @ {{instance}} ","metric":"","query":"sum(rate(compactor_iter_scan_key_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compactor Iter keys","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"bytes of Lsm tree needed to reach balance","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":163,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_pending_bytes{job=~\"$job\",instance=~\"$node\"}) by (instance, group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compact pending bytes - {{group}} @ {{instance}} ","metric":"","query":"sum(storage_compact_pending_bytes{job=~\"$job\",instance=~\"$node\"}) by (instance, group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Compact Pending Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"compression ratio of each level of the lsm tree","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":164,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_compact_level_compression_ratio{job=~\"$job\",instance=~\"$node\"}) by (instance, group, level, algorithm)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"lsm compression ratio - cg{{group}} @ L{{level}} - {{algorithm}} {{instance}}","metric":"","query":"sum(storage_compact_level_compression_ratio{job=~\"$job\",instance=~\"$node\"}) by (instance, group, level, algorithm)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lsm Level Compression Ratio","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Compaction","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":13},"height":null,"hideTimeOverride":false,"id":165,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":166,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":167,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(object_store_operation_latency_bucket{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, type, job, instance)(rate(object_store_operation_latency_sum{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(object_store_operation_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} avg - {{job}} @ {{instance}}","metric":"","query":"sum by(le, type, job, instance)(rate(object_store_operation_latency_sum{type!~'streaming_upload_write_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(object_store_operation_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":168,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type!~'streaming_upload_write_bytes|streaming_read_read_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_operation_latency_count{type!~'streaming_upload_write_bytes|streaming_read_read_bytes|streaming_read',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'upload|delete',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-write - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_operation_latency_count{type=~'upload|delete',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_operation_latency_count{type=~'read|readv|list|metadata',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{media_type}}-read - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_operation_latency_count{type=~'read|readv|list|metadata',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, media_type, job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":169,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(object_store_operation_bytes_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, type, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":170,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Failure Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":171,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_request_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} - {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_request_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Operation Retry Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"There are two types of operations: 1. GET, SELECT, and DELETE, they cost 0.0004 USD per 1000 requests. 2. PUT, COPY, POST, LIST, they cost 0.005 USD per 1000 requests.Reading from S3 across different regions impose extra cost. This metric assumes 0.01 USD per 1GB data transfer. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":172,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}) * 0.01 / 1000 / 1000 / 1000","format":"time_series","hide":true,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"(Cross Region) Data Transfer Cost","metric":"","query":"sum(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}) * 0.01 / 1000 / 1000 / 1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'read|streaming_read_start|streaming_read_init',job=~\"$job\",instance=~\"$node\"}) * 0.0004 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GET, SELECT, and all other Requests Cost","metric":"","query":"sum(object_store_operation_latency_count{type=~'read|streaming_read_start|streaming_read_init',job=~\"$job\",instance=~\"$node\"}) * 0.0004 / 1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(object_store_operation_latency_count{type=~'upload|streaming_upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|list',job=~\"$job\",instance=~\"$node\"}) * 0.005 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"PUT, COPY, POST, LIST Requests Cost","metric":"","query":"sum(object_store_operation_latency_count{type=~'upload|streaming_upload|streaming_upload_start|s3_upload_part|streaming_upload_finish|list',job=~\"$job\",instance=~\"$node\"}) * 0.005 / 1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Realtime)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"This metric uses the total size of data in S3 at this second to derive the cost of storing data for a whole month. The price is 0.023 USD per GB. Please checkout AWS's pricing model for more accurate calculation.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"$"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":173,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance) * 0.023 / 1000 / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Monthly Storage Cost","metric":"","query":"sum(storage_level_total_file_size{job=~\"$job\",instance=~\"$node\"}) by (instance) * 0.023 / 1000 / 1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Estimated S3 Cost (Monthly)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Object Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":14},"height":null,"hideTimeOverride":false,"id":174,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":175,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_hybrid_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_hybrid_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hybrid Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":176,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - hybrid - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_hybrid_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hybrid Cache Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":177,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_hybrid_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - hybrid - hit ratio @ {{instance}}","metric":"","query":"sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_hybrid_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_hybrid_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Hybrid Cache Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":178,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_memory_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - memory - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_memory_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":179,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(foyer_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - memory - size @ {{instance}}","metric":"","query":"sum(foyer_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Cache Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":180,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_memory_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - memory - hit ratio @ {{instance}}","metric":"","query":"sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_memory_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_memory_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Cache Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":181,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":182,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_inner_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_inner_op_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Inner Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":183,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_storage_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":184,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - storage - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_storage_inner_op_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Inner Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":185,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_storage_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - storage - hit ratio @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) / (sum(rate(foyer_storage_op_total{op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance) + sum(rate(foyer_storage_op_total{op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache Hit Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":186,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(foyer_storage_region{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance) * on(name, instance) group_left() avg(foyer_storage_region_size_bytes{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - {{type}} region - size @ {{instance}}","metric":"","query":"sum(foyer_storage_region{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance) * on(name, instance) group_left() avg(foyer_storage_region_size_bytes{job=~\"$job\",instance=~\"$node\"}) by (name, type, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Region Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":187,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_disk_io_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_disk_io_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Disk Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":188,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(foyer_storage_disk_io_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, name, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Disk Op Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":189,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(foyer_storage_disk_io_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{name}} - disk - {{op}} @ {{instance}}","metric":"","query":"sum(rate(foyer_storage_disk_io_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (name, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Disk Op Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":190,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=~\"meta|data\",op!~\"filtered|ignored\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache refill - {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=~\"meta|data\",op!~\"filtered|ignored\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (type, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":191,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{type}} file cache - {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (foyer, op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Data Refill Throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":192,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{foyer}} cache refill - {{op}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(refill_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, foyer, op, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":193,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(refill_queue_total) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"refill queue length @ {{instance}}","metric":"","query":"sum(refill_queue_total) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Refill Queue Length","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":72},"height":null,"hideTimeOverride":false,"id":194,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"parent_meta\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":80},"height":null,"hideTimeOverride":false,"id":195,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"parent_meta\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"parent meta lookup hit ratio @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"parent_meta\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"parent_meta\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Parent Meta Lookup Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":80},"height":null,"hideTimeOverride":false,"id":196,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"unit_inheritance\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":88},"height":null,"hideTimeOverride":false,"id":197,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"unit_inheritance\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unit inheritance ratio @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / (sum(rate(refill_total{type=\"unit_inheritance\",op=\"hit\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) + sum(rate(refill_total{type=\"unit_inheritance\",op=\"miss\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inheritance - Unit inheritance Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":88},"height":null,"hideTimeOverride":false,"id":198,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill {{op}} @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"block\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":96},"height":null,"hideTimeOverride":false,"id":199,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(refill_total{type=\"block\",op=\"success\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / sum(rate(refill_total{type=\"block\",op=\"unfiltered\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) >= 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"block refill ratio @ {{instance}}","metric":"","query":"sum(rate(refill_total{type=\"block\",op=\"success\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) / sum(rate(refill_total{type=\"block\",op=\"unfiltered\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance) >= 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Block Refill Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Item numbers of the recent filter.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":96},"height":null,"hideTimeOverride":false,"id":200,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(recent_filter_items{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"items @ {{instance}}","metric":"","query":"sum(recent_filter_items{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recent Filter Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":104},"height":null,"hideTimeOverride":false,"id":201,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recent_filter_ops{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"recent filter {{op}} @ {{instance}}","metric":"","query":"sum(rate(recent_filter_ops{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Recent Filter Ops","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Tiered Cache","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":15},"height":null,"hideTimeOverride":false,"id":202,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":203,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p50 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","query":"histogram_quantile(0.5, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time p99 - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","query":"histogram_quantile(0.99, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lock Time pmax - {{method}} @ {{lock_type}} @ {{lock_name}}","metric":"","query":"histogram_quantile(1.0, sum(rate(hummock_manager_lock_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method, lock_name, lock_type))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Lock Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":204,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p50 - {{method}}","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time p99 - {{method}}","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Real Process Time pmax - {{method}}","metric":"","query":"histogram_quantile(1.0, sum(rate(meta_hummock_manager_real_process_time_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, method))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Real Process Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":205,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version size","metric":"","query":"storage_version_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":206,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"current version id","metric":"","query":"storage_current_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"checkpoint version id","metric":"","query":"storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned version id","metric":"","query":"storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_safepoint_version_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min safepoint version id","metric":"","query":"storage_min_safepoint_version_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Id","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":207,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"max committed epoch","metric":"","query":"storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_safe_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"safe epoch","metric":"","query":"storage_safe_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"min pinned epoch","metric":"","query":"storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":208,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","query":"storage_version_stats{metric='total_key_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_value_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","query":"storage_version_stats{metric='total_value_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":209,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","query":"storage_materialized_view_stats{metric='materialized_view_total_size',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":210,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_version_stats{metric='total_key_count',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table{{table_id}} {{metric}}","metric":"","query":"storage_version_stats{metric='total_key_count',table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Table KV Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\nObjects are classified into 3 groups:\n- not referenced by versions: these object are being deleted from object store.\n- referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n- referenced by current version: these objects are in the latest version.\n\nAdditionally, a metric on all objects (including dangling ones) is updated with low-frequency. The metric is updated right before full GC. So subsequent full GC may reduce the actual value significantly, without updating the metric.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":211,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects (including dangling ones)","metric":"","query":"storage_total_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Refer to `Object Total Number` panel for classification of objects.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":212,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_total_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"all objects, including dangling ones","metric":"","query":"storage_total_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of hummock version delta log","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":213,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_delta_log_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"delta log total number","metric":"","query":"storage_delta_log_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Delta Log Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"hummock version checkpoint latency","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":214,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p90","metric":"","query":"histogram_quantile(0.9, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.999, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_p999","metric":"","query":"histogram_quantile(0.999, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_pmax","metric":"","query":"histogram_quantile(1.0, sum(rate(storage_version_checkpoint_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(storage_version_checkpoint_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(storage_version_checkpoint_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"version_checkpoint_latency_avg","metric":"","query":"rate(storage_version_checkpoint_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(storage_version_checkpoint_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Version Checkpoint Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"When certain per compaction group threshold is exceeded (e.g. number of level 0 sub-level in LSMtree), write op to that compaction group is stopped temporarily. Check log for detail reason of write stop.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":215,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compaction_group_{{compaction_group_id}}","metric":"","query":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Stop Compaction Groups","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"total number of attempts to trigger full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":216,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_trigger_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_trigger_count","metric":"","query":"storage_full_gc_trigger_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Trigger Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"the object id watermark used in last full GC","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":217,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_full_gc_last_object_id_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"full_gc_last_object_id_watermark","metric":"","query":"storage_full_gc_last_object_id_watermark{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Full GC Last Watermark","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":218,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta consumed latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(storage_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"meta iteration latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(storage_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor consumed latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_consumed_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"compactor iteration latency pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(irate(compactor_compaction_event_loop_iteration_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Compaction Event Loop Time","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The times of move_state_table occurs","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":219,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_move_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"move table cg{{group}}","metric":"","query":"sum(storage_move_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}) by (group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Move State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of state_tables in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":220,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"state table cg{{group}}","metric":"","query":"sum(irate(storage_state_table_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of branched_sst in each CG","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":221,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(storage_branched_sst_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"branched sst cg{{group}}","metric":"","query":"sum(irate(storage_branched_sst_count{table_id=~\"$table|\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (group)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Branched SST Count","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":222,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total backup job count since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":223,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"backup_job_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"job count","metric":"","query":"backup_job_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Latency of backup jobs since the Meta node starts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":224,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p50 - {{state}}","metric":"","query":"histogram_quantile(0.5, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time p99 - {{state}}","metric":"","query":"histogram_quantile(0.99, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Job Process Time pmax - {{state}}","metric":"","query":"histogram_quantile(1.0, sum(rate(backup_job_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, state))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Job Process Time","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Backup Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":17},"height":null,"hideTimeOverride":false,"id":225,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":226,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Create_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Create',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":227,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Drop_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/Drop',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Drop latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":228,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetCatalog_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.CatalogService/GetCatalog',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetCatalog latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Catalog Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":229,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":230,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"AddWorkerNode_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/AddWorkerNode',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"AddWorkerNode latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":231,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ListAllNodes_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.ClusterService/ListAllNodes',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ListAllNodes latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Cluster Service","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":19},"height":null,"hideTimeOverride":false,"id":232,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":233,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"CreateMaterializedView_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/CreateMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"CreateMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":234,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"DropMaterializedView_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/DropMaterializedView',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"DropMaterializedView latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":235,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.StreamManagerService/Flush',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Flush latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Stream Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":20},"height":null,"hideTimeOverride":false,"id":236,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":237,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinVersionBefore_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinVersionBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinVersionBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":8,"y":0},"height":null,"hideTimeOverride":false,"id":238,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"UnpinSnapshotBefore_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/UnpinSnapshotBefore',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UnpinSnapshotBefore latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":16,"y":0},"height":null,"hideTimeOverride":false,"id":239,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"ReportCompactionTasks_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/ReportCompactionTasks',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"ReportCompactionTasks latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":8,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":240,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p50","metric":"","query":"histogram_quantile(0.5, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p90","metric":"","query":"histogram_quantile(0.9, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_p99","metric":"","query":"histogram_quantile(0.99, sum(irate(meta_grpc_duration_seconds_bucket{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"GetNewSstIds_avg","metric":"","query":"sum(irate(meta_grpc_duration_seconds_sum{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(meta_grpc_duration_seconds_count{path='/meta.HummockManagerService/GetNewSstIds',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"GetNewSstIds latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC Meta: Hummock Manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":21},"height":null,"hideTimeOverride":false,"id":241,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":242,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_counts - {{instance}}","metric":"","query":"sum(irate(state_store_report_compaction_task_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":243,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_version_before_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_version_before_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_avg","metric":"","query":"sum(irate(state_store_unpin_version_before_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_version_before_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_version_before_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_unpin_version_before_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"version_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":244,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latencyp90 - {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(irate(state_store_pin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_latency_avg","metric":"","query":"sum(irate(state_store_pin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_pin_snapshot_latency_count[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_unpin_version_snapshot_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_avg","metric":"","query":"sum(irate(state_store_unpin_snapshot_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_unpin_snapshot_latency_count[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_unpin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_unpin_snapshot_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":245,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_pin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pin_snapshot_counts - {{instance}}","metric":"","query":"sum(irate(state_store_pin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_unpin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"unpin_snapshot_counts - {{instance}}","metric":"","query":"sum(irate(state_store_unpin_snapshot_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"snapshot_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":246,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_get_new_sst_ids_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_avg","metric":"","query":"sum(irate(state_store_get_new_sst_ids_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_get_new_sst_ids_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_get_new_sst_ids_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":247,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_get_new_sst_ids_latency_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"get_new_sst_ids_latency_counts - {{instance}}","metric":"","query":"sum(irate(state_store_get_new_sst_ids_latency_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))by(job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"table_count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":248,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(state_store_report_compaction_task_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_avg","metric":"","query":"sum(irate(state_store_report_compaction_task_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum(irate(state_store_report_compaction_task_latency_count[$__rate_interval])) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"report_compaction_task_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(state_store_report_compaction_task_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"compaction_latency","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"gRPC: Hummock Meta Client","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":22},"height":null,"hideTimeOverride":false,"id":249,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":250,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":251,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":252,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Per Second (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":253,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","query":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Running Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":254,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","query":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Rejected queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":255,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","query":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The Number of Completed Queries (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":256,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Distributed Query Mode)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":257,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency (Local Query Mode)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Frontend","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":23},"height":null,"hideTimeOverride":false,"id":258,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":259,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(lru_runtime_loop_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"rate(lru_runtime_loop_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager loop count per sec","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":260,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_eviction_policy{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"lru_eviction_policy{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager eviction policy","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":261,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_latest_sequence{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"lru_latest_sequence{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_watermark_sequence{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"lru_watermark_sequence{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager sequence","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":262,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":263,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_active_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":264,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_resident_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_resident_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The resident memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":265,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jemalloc_metadata_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jemalloc_metadata_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The metadata memory of jemalloc","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":266,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jvm_allocated_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The allocated memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":267,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"jvm_active_bytes{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"jvm_active_bytes{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"The active memory of jvm","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":268,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"} - on() group_right() lru_evicted_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"table {{table_id}} actor {{actor_id}} desc: {{desc}}","metric":"","query":"lru_current_watermark_time_ms{job=~\"$job\",instance=~\"$node\"} - on() group_right() lru_evicted_watermark_time_ms{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"LRU manager diff between current watermark and evicted watermark time (ms) for actors","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory manager","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":269,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":270,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","query":"rate(source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":271,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","query":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":25},"height":null,"hideTimeOverride":false,"id":272,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":273,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{connector}} {{sink_id}}","metric":"","query":"histogram_quantile(0.5, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{connector}} {{sink_id}}","metric":"","query":"histogram_quantile(0.99, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{connector}} {{sink_id}}","metric":"","query":"histogram_quantile(1.0, sum(rate(sink_commit_duration_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, connector, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, connector, sink_id)(rate(sink_commit_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(sink_commit_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{connector}} @ {{sink_id}}","metric":"","query":"sum by(le, connector, sink_id)(rate(sink_commit_duration_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(sink_commit_duration_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Commit Duration","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":274,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest write epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest read epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_min_epoch{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Kv log store uncomsuned min epoch @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_min_epoch{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Read/Write Epoch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":275,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(max(log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"(max(log_store_latest_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":276,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(log_store_reader_wait_new_future_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id, executor_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Backpressure @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"avg(rate(log_store_reader_wait_new_future_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id, executor_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Backpressure Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":277,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"clamp_min((max(log_store_first_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000, 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Consume persistent log lag @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"clamp_min((max(log_store_first_write_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)- max(log_store_latest_read_epoch{job=~\"$job\",instance=~\"$node\"}) by (connector, sink_id, executor_id)) / (2^16) / 1000, 0)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Persistent Log Lag","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":278,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","query":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":279,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","query":"sum(rate(log_store_read_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Consume Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":280,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}}","metric":"","query":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":281,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector}} {{sink_id}} @ {{executor_id}} {{instance}}","metric":"","query":"sum(rate(log_store_write_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, connector, sink_id, executor_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Log Store Write Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":282,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_read_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":283,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_read_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_read_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Read Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":284,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_write_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Row Ops","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":285,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_storage_write_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_storage_write_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Write Storage Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":286,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_item_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Unconsumed item count @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_item_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_row_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Unconsumed row count @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_row_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"kv_log_store_buffer_unconsumed_epoch_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Unconsumed epoch count @ {{connector}} {{sink_id}} {{executor_id}}","metric":"","query":"kv_log_store_buffer_unconsumed_epoch_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Buffer State","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":287,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(kv_log_store_rewind_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"sum(rate(kv_log_store_rewind_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (executor_id, connector, sink_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kv Log Store Rewind Rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":288,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(kv_log_store_rewind_delay_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor_id, connector, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} - {{connector}} @ {{sink_id}}","metric":"","query":"histogram_quantile(1.0, sum(rate(kv_log_store_rewind_delay_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, executor_id, connector, sink_id))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rewind delay (second)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total size of chunks buffered in a barrier","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":289,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_sink_chunk_buffer_size{job=~\"$job\",instance=~\"$node\"}) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}} - actor {{actor_id}}","metric":"","query":"sum(stream_sink_chunk_buffer_size{job=~\"$job\",instance=~\"$node\"}) by (sink_id, actor_id) * on(actor_id) group_left(sink_name) sink_info{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Chunk Buffer Size","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":290,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Kafka high watermark by source and partition and source latest message by partition, source and actor","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":291,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"high watermark: source={{source_id}} partition={{partition}}","metric":"","query":"source_kafka_high_watermark{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_latest_message_id{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"latest msg: source={{source_id}} partition={{partition}} actor_id={{actor_id}}","metric":"","query":"source_latest_message_id{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Kafka high watermark and source latest message","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current number of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":292,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_msg_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Current total size of messages in producer queues","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":293,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_msg_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_msg_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Size in Producer Queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages transmitted (produced) to Kafka brokers","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":294,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_tx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_tx_msgs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Produced Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of messages consumed, not including ignored messages (due to offset, etc), from Kafka brokers.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":295,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_top_rx_msgs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id }}","metric":"","query":"rdkafka_top_rx_msgs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Received Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages awaiting transmission to broker","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":296,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_outbuf_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_outbuf_msg_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message Count Pending to Transmit (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages in-flight to broker awaiting response","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":297,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_waitresp_msg_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_waitresp_msg_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Inflight Message Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of transmission errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":298,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_tx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_tx_errs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Transmitting (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of receive errors","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":32},"height":null,"hideTimeOverride":false,"id":299,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rx_errs{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_rx_errs{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Error Count When Receiving (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of requests timed out","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":32},"height":null,"hideTimeOverride":false,"id":300,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_req_timeouts{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, state {{ state }}","metric":"","query":"rdkafka_broker_req_timeouts{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Timeout Request Count (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker latency / round-trip time in milli seconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":40},"height":null,"hideTimeOverride":false,"id":301,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_avg{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p75{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p90{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_rtt_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_rtt_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"RTT (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Broker throttling time in milliseconds","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":40},"height":null,"hideTimeOverride":false,"id":302,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_avg{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_avg{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p75{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p75{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p90{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p90{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_p99_99{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_broker_throttle_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}","metric":"","query":"rdkafka_broker_throttle_out_of_range{job=~\"$job\",instance=~\"$node\"}/1000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Throttle Time (per broker)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Age of metadata from broker for this topic (milliseconds)","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ms"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":303,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_metadata_age{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}","metric":"","query":"rdkafka_topic_metadata_age{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Metadata_age Age","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch sizes in bytes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":56},"height":null,"hideTimeOverride":false,"id":304,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_avg{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p75{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p90{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_p99_99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchsize_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchsize_out_of_range{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Batch message counts","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":48},"height":null,"hideTimeOverride":false,"id":null,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_avg{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_avg{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p75{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p75{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p90{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p90{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_p99_99{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_p99_99{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_batchcnt_out_of_range{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, broker {{ broker }}, topic {{ topic }}","metric":"","query":"rdkafka_topic_batchcnt_out_of_range{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Messages","transformations":[],"transparent":false,"type":"timeseries"}],"timeFrom":null,"timeShift":null,"title":"Topic Batch Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of messages ready to be produced in transmit queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":56},"height":null,"hideTimeOverride":false,"id":305,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_xmit_msgq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_xmit_msgq_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message to be Transmitted","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of pre-fetched messages in fetch queue","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":64},"height":null,"hideTimeOverride":false,"id":306,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_fetchq_cnt{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_fetchq_cnt{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Message in pre fetch queue","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Next offset to fetch","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":64},"height":null,"hideTimeOverride":false,"id":307,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_next_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_next_offset{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Next offset to fetch","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Last committed offset","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":72},"height":null,"hideTimeOverride":false,"id":308,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rdkafka_topic_partition_committed_offset{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"id {{ id }}, client_id {{ client_id}}, topic {{ topic }}, partition {{ partition }}","metric":"","query":"rdkafka_topic_partition_committed_offset{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Committed Offset","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Kafka Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":27},"height":null,"hideTimeOverride":false,"id":309,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":310,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Network throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":311,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"S3 throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":312,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total read @ {{instance}}","metric":"","query":"sum(rate(connection_read_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total write @ {{instance}}","metric":"","query":"sum(rate(connection_write_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"gRPC throughput","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":313,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_io_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","query":"sum(irate(connection_io_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} grpc {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","query":"sum(rate(connection_io_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(connection_io_err_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} total {{op_type}} err[{{error_kind}}] @ {{instance}}","metric":"","query":"sum(rate(connection_io_err_rate{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, op_type, error_kind)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"IO error rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":314,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","query":"sum(connection_count{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(connection_count{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","query":"sum(connection_count{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}) by (job, instance, connection_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Existing connection count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":315,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","query":"sum(irate(connection_create_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_create_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","query":"sum(irate(connection_create_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection rate","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":316,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} S3 @ {{instance}}","metric":"","query":"sum(irate(connection_err_rate{connection_type=\"S3\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(connection_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} {{connection_type}} @ {{instance}}","metric":"","query":"sum(irate(connection_err_rate{connection_type=~\"grpc.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, connection_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Create new connection err rate","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network connection","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":28},"height":null,"hideTimeOverride":false,"id":317,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"iceberg write qps","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":318,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_write_qps{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_write_qps{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Qps Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":319,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 @ {{sink_id}}","metric":"","query":"histogram_quantile(0.5, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 @ {{sink_id}}","metric":"","query":"histogram_quantile(0.99, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax @ {{sink_id}}","metric":"","query":"histogram_quantile(1.0, sum(rate(iceberg_write_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, sink_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, sink_id)(rate(iceberg_write_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(iceberg_write_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg @ {{sink_id}}","metric":"","query":"sum by(le, sink_id)(rate(iceberg_write_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, type, job, instance) (rate(iceberg_write_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Latency Of Iceberg Writer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":320,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_rolling_unfushed_data_file{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_rolling_unfushed_data_file{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg rolling unfushed data file","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":321,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_position_delete_cache_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_position_delete_cache_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg position delete cache num","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":322,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"iceberg_partition_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{executor_id}} @ {{sink_id}}","metric":"","query":"iceberg_partition_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Iceberg partition num","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Iceberg Sink Metrics","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":29},"height":null,"hideTimeOverride":false,"id":323,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":324,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{instance}}","metric":"","query":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{instance}}","metric":"","query":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","query":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_success_count - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_success_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_failure_count - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_retry_count - {{instance}}","metric":"","query":"sum(rate(udf_retry_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Calls Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":325,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_input_chunk_rows_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_input_chunk_rows_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_input_chunk_rows_avg - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(irate(udf_input_chunk_rows_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_input_chunk_rows_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Input Chunk Rows","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":326,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.50, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p50 - {{instance}}","metric":"","query":"histogram_quantile(0.50, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.90, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p90 - {{instance}}","metric":"","query":"histogram_quantile(0.90, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99 - {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg - {{instance}}","metric":"","query":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, link, name, fragment_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_p99_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","query":"histogram_quantile(0.99, sum(irate(udf_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, link, name, fragment_id))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_latency_avg_by_name - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(irate(udf_latency_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / sum(irate(udf_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":327,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{instance}}","metric":"","query":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_rows - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_input_rows{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":328,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{instance}}","metric":"","query":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance) / (1024*1024)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / (1024*1024)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_throughput_bytes - {{link}} {{name}} {{fragment_id}}","metric":"","query":"sum(rate(udf_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (link, name, fragment_id) / (1024*1024)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Throughput (bytes)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Currently only embedded JS UDF supports this. Others will always show 0.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":329,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_memory_usage - {{instance}}","metric":"","query":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (job, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"udf_memory_usage - {{name}} {{fragment_id}}","metric":"","query":"sum(udf_memory_usage{job=~\"$job\",instance=~\"$node\"}) by (name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"UDF Memory Usage (bytes)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"User Defined Function","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(table_info, table_id)","description":"Reporting table id of the metric","hide":0,"includeAll":true,"label":"Table","multi":true,"name":"table","options":[],"query":{"query":"label_values(table_info, table_id)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"nowDelay":null,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dev_dashboard","uid":"Ecy3uV1nz","version":0} diff --git a/grafana/risingwave-user-dashboard.dashboard.py b/grafana/risingwave-user-dashboard.dashboard.py index b476de74f0f88..20ba4a857eaca 100644 --- a/grafana/risingwave-user-dashboard.dashboard.py +++ b/grafana/risingwave-user-dashboard.dashboard.py @@ -49,6 +49,13 @@ def section_actor_info(outer_panels): [panels.table_target(f"group({metric('table_info')}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)")], ["table_id", "table_name", "table_type", "materialized_view_id", "fragment_id", "compaction_group_id"], ), + panels.table_info( + "Actor Count (Group By Compute Node)", + "Actor count per compute node", + [panels.table_target(f"count({metric('actor_info')}) by (compute_node)")], + ["table_id", "table_name", "table_type", "materialized_view_id", "fragment_id", "compaction_group_id"], + dict.fromkeys(["Time"], True) + ) ], ) ] @@ -113,7 +120,7 @@ def section_overview(panels): ) + [ panels.target( - f"rate({metric('meta_barrier_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_duration_seconds_count')}[$__rate_interval])", + f"rate({metric('meta_barrier_duration_seconds_sum')}[$__rate_interval]) / rate({metric('meta_barrier_duration_seconds_count')}[$__rate_interval]) > 0", "barrier_latency_avg", ), ], @@ -409,35 +416,35 @@ def section_memory(outer_panels): "", [ panels.target( - f"(sum(rate({metric('stream_join_lookup_miss_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate({metric('stream_join_lookup_total_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id))", + f"(sum(rate({metric('stream_join_lookup_miss_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate({metric('stream_join_lookup_total_count')}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id)) >=0 ", "join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}", ), panels.target( - f"(sum(rate({metric('stream_agg_lookup_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_agg_lookup_total_count')}[$__rate_interval])) by (table_id, actor_id))", + f"(sum(rate({metric('stream_agg_lookup_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_agg_lookup_total_count')}[$__rate_interval])) by (table_id, actor_id)) >=0 ", "Agg cache miss ratio - table {{table_id}} actor {{actor_id}} ", ), panels.target( - f"(sum(rate({metric('stream_agg_distinct_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_agg_distinct_total_cache_count')}[$__rate_interval])) by (table_id, actor_id))", + f"(sum(rate({metric('stream_agg_distinct_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_agg_distinct_total_cache_count')}[$__rate_interval])) by (table_id, actor_id)) >=0", "Distinct agg cache miss ratio - table {{table_id}} actor {{actor_id}} ", ), panels.target( - f"(sum(rate({metric('stream_group_top_n_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_group_top_n_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id))", + f"(sum(rate({metric('stream_group_top_n_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_group_top_n_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id)) >=0", "Stream group top n cache miss ratio - table {{table_id}} actor {{actor_id}} ", ), panels.target( - f"(sum(rate({metric('stream_group_top_n_appendonly_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_group_top_n_appendonly_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id))", + f"(sum(rate({metric('stream_group_top_n_appendonly_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_group_top_n_appendonly_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id)) >=0", "Stream group top n appendonly cache miss ratio - table {{table_id}} actor {{actor_id}} ", ), panels.target( - f"(sum(rate({metric('stream_lookup_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_lookup_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id))", + f"(sum(rate({metric('stream_lookup_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_lookup_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id)) >=0", "Stream lookup cache miss ratio - table {{table_id}} actor {{actor_id}} ", ), panels.target( - f"(sum(rate({metric('stream_temporal_join_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_temporal_join_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id))", + f"(sum(rate({metric('stream_temporal_join_cache_miss_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_temporal_join_total_query_cache_count')}[$__rate_interval])) by (table_id, actor_id)) >=0", "Stream temporal join cache miss ratio - table {{table_id}} actor {{actor_id}} ", ), panels.target( - f"1 - (sum(rate({metric('stream_materialize_cache_hit_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_materialize_cache_total_count')}[$__rate_interval])) by (table_id, actor_id))", + f"1 - (sum(rate({metric('stream_materialize_cache_hit_count')}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate({metric('stream_materialize_cache_total_count')}[$__rate_interval])) by (table_id, actor_id)) >=0", "materialize executor cache miss ratio - table {{table_id}} - actor {{actor_id}} {{%s}}" % NODE_LABEL, ), @@ -648,7 +655,7 @@ def section_storage(outer_panels): [50, 99], ), panels.target( - f"sum by(le, {COMPONENT_LABEL}) (rate({metric('state_store_sync_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}) (rate({metric('state_store_sync_size_count')}[$__rate_interval]))", + f"sum by(le, {COMPONENT_LABEL}) (rate({metric('state_store_sync_size_sum')}[$__rate_interval])) / sum by(le, {COMPONENT_LABEL}) (rate({metric('state_store_sync_size_count')}[$__rate_interval])) > 0", "avg - {{%s}}" % COMPONENT_LABEL, ), ], @@ -785,17 +792,14 @@ def section_batch(outer_panels): "Query Latency in Distributed Execution Mode", "", [ - panels.target( - f"histogram_quantile(0.5, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p50 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.9, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p90 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.95, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p99 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), + *quantile( + lambda quantile, legend: panels.target( + f"histogram_quantile({quantile}, sum(rate({metric('distributed_query_latency_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", + f"p{legend}" + + " - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + [50, 90, 99, "max"], ), ], ), @@ -803,17 +807,14 @@ def section_batch(outer_panels): "Query Latency in Local Execution Mode", "", [ - panels.target( - f"histogram_quantile(0.5, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p50 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.9, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p90 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), - ), - panels.target( - f"histogram_quantile(0.95, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", - "p99 - {{%s}} @ {{%s}}" % (COMPONENT_LABEL, NODE_LABEL), + *quantile( + lambda quantile, legend: panels.target( + f"histogram_quantile({quantile}, sum(rate({metric('frontend_latency_local_execution_bucket')}[$__rate_interval])) by (le, {COMPONENT_LABEL}, {NODE_LABEL}))", + f"p{legend}" + + " - {{%s}} @ {{%s}}" + % (COMPONENT_LABEL, NODE_LABEL), + ), + [50, 90, 99, "max"], ), ], ), diff --git a/grafana/risingwave-user-dashboard.json b/grafana/risingwave-user-dashboard.json index 5fbc9681be606..037679704e38f 100644 --- a/grafana/risingwave-user-dashboard.json +++ b/grafana/risingwave-user-dashboard.json @@ -1 +1 @@ -{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dashboard","editable":true,"gnetId":null,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]}},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true},"repeat":null,"repeatDirection":null,"span":6,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":false,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Overview","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":5,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":2},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":10},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":9,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Alerts in the system group by type:\n - Too Many Barriers: there are too many uncommitted barriers generated. This means the streaming graph is stuck or under heavy load. Check 'Barrier Latency' panel.\n - Recovery Triggered: cluster recovery is triggered. Check 'Errors by Type' / 'Node Count' panels.\n - Lagging Version: the checkpointed or pinned version id is lagging behind the current version id. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Epoch: the pinned or safe epoch is lagging behind the current max committed epoch. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Compaction: there are too many files in L0. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Lagging Vacuum: there are too many stale files waiting to be cleaned. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Abnormal Meta Cache Memory: the meta cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Block Cache Memory: the block cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Uploading Memory Usage: uploading memory is more than 70 percent of the expected, and is about to spill.\n - Write Stall: Compaction cannot keep up. Stall foreground write.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":18},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Too Many Barriers","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > bool 0 + sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recovery Triggered","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100) + ((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Version","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}) >= bool 6553600000 unless + storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"} == 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Epoch","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(label_replace(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}, 'L0', 'L0', 'level_index', '.*_L0') unless storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (L0) >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Compaction","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Vacuum","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_meta_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Meta Cache Memory","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_block_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Block Cache Memory","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_uploading_memory_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 0.7","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Uploading Memory Usage","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"} > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Write Stall","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Alerts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors in the system group by type","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"} == 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source error: source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote storage error {{type}}: {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Errors","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":26},"height":null,"hideTimeOverride":false,"id":12,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Local mode","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distributed mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Query QPS","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":34},"height":null,"hideTimeOverride":false,"id":13,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions in frontend nodes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":34},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":42},"height":null,"hideTimeOverride":false,"id":15,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":16,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of CPU cores per RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Core Number","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"CPU","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":43},"height":null,"hideTimeOverride":false,"id":18,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":19,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Total)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(actor_memory_usage[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"streaming actor - {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage meta cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage block cache - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage write buffer - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized_view {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Detailed)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Executor cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - cache miss - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - total lookups - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - cache miss - table_id {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - total lookups - table_id {{table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - cache hit count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - total cache count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialize executor cache miss ratio - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"memory cache - {{table_id}} @ {{type}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage bloom filter statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_check_bloom_filter_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter total - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_positive_but_non_exist_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive - {{table_id}} @ {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Bloom Filer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage file cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache {{op}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_miss{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache miss @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage File Cache","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":44},"height":null,"hideTimeOverride":false,"id":27,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Send/Recv throughput per node for streaming exchange","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":28,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Send @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recv @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Streming Remote Exchange (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput per node","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Exchange Recv (Rows/s)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":45},"height":null,"hideTimeOverride":false,"id":31,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":32,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The storage size of each materialized view","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Compaction refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Compaction - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":36,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Size statistics for checkpoint","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Size","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":46},"height":null,"hideTimeOverride":false,"id":38,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":39,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id)(rate(partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":41,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized executor actor per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{executor_identity=~\".*MaterializeExecutor.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(actor_id) group_left(materialized_view_id, table_name) (group(table_info{table_type=~\"MATERIALIZED_VIEW\",job=~\"$job\",instance=~\"$node\"}) by (actor_id, materialized_view_id, table_name))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{table_name}} table_id {{materialized_view_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill operator used by MV on MV","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Read Snapshot - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Upstream - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":44,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":47},"height":null,"hideTimeOverride":false,"id":45,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":46,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Running query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rejected query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Completed query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Distributed Execution Mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.95, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Local Execution Mode","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":51,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":52,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dashboard","uid":"Fcy3uV1nz","version":0} +{"__inputs":[],"annotations":{"list":[]},"description":"RisingWave Dashboard","editable":true,"gnetId":null,"graphTooltip":0,"hideControls":false,"id":null,"links":[],"panels":[{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":1,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about actors","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":2,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(actor_info{job=~\"$job\",instance=~\"$node\"}) by (actor_id, fragment_id, compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"actor_id":0,"compute_node":2,"fragment_id":1}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Information about state tables. Column `materialized_view_id` is the id of the materialized view that this state table belongs to.","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":3,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name, table_type, materialized_view_id, fragment_id, compaction_group_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"State Table Info","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true,"Value":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"},{"cacheTimeout":null,"color":{"mode":"thresholds"},"columns":[],"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Actor count per compute node","editable":true,"error":false,"fieldConfig":{"defaults":{"custom":{"align":"auto","displayMode":"auto","filterable":true},"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"fontSize":"100%","gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":4,"interval":null,"links":[],"mappings":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"options":{"showHeader":true,"sortBy":[]},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","format":"table","hide":false,"instant":true,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"count(actor_info{job=~\"$job\",instance=~\"$node\"}) by (compute_node)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Count (Group By Compute Node)","transformations":[{"id":"organize","options":{"excludeByName":{"Time":true},"indexByName":{"compaction_group_id":5,"fragment_id":4,"materialized_view_id":3,"table_id":0,"table_name":1,"table_type":2}}}],"transparent":false,"type":"table"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Actor/Table Id Info","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":false,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":1},"height":null,"hideTimeOverride":false,"id":5,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Overview","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":2},"height":null,"hideTimeOverride":false,"id":6,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":2},"height":null,"hideTimeOverride":false,"id":7,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"(sum by (source_id, source_name, fragment_id)(rate(source_partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of rows streamed into each sink per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":10},"height":null,"hideTimeOverride":false,"id":8,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink {{sink_id}} {{sink_name}}","metric":"","query":"sum(rate(stream_sink_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (sink_id) * on(sink_id) group_left(sink_name) group(sink_info{job=~\"$job\",instance=~\"$node\"}) by (sink_id, sink_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Sink Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized view per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":10},"height":null,"hideTimeOverride":false,"id":9,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"mview {{table_id}} {{table_name}}","metric":"","query":"sum(rate(stream_mview_input_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id) * on(table_id) group_left(table_name) group(table_info{job=~\"$job\",instance=~\"$node\"}) by (table_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The time that the data between two consecutive barriers gets fully processed, i.e. the computation results are made durable into materialized views or sink to external systems. This metric shows to users the freshness of materialized views.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":18},"height":null,"hideTimeOverride":false,"id":10,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p50","metric":"","query":"histogram_quantile(0.5, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_p99","metric":"","query":"histogram_quantile(0.99, sum(rate(meta_barrier_duration_seconds_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"barrier_latency_avg","metric":"","query":"rate(meta_barrier_duration_seconds_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) / rate(meta_barrier_duration_seconds_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Barrier Latency","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Alerts in the system group by type:\n - Too Many Barriers: there are too many uncommitted barriers generated. This means the streaming graph is stuck or under heavy load. Check 'Barrier Latency' panel.\n - Recovery Triggered: cluster recovery is triggered. Check 'Errors by Type' / 'Node Count' panels.\n - Lagging Version: the checkpointed or pinned version id is lagging behind the current version id. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Epoch: the pinned or safe epoch is lagging behind the current max committed epoch. Check 'Hummock Manager' section in dev dashboard.\n - Lagging Compaction: there are too many files in L0. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Lagging Vacuum: there are too many stale files waiting to be cleaned. This can be caused by compactor failure or lag of compactor resource. Check 'Compaction' section in dev dashboard.\n - Abnormal Meta Cache Memory: the meta cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Block Cache Memory: the block cache memory usage is too large, exceeding the expected 10 percent.\n - Abnormal Uploading Memory Usage: uploading memory is more than 70 percent of the expected, and is about to spill.\n - Write Stall: Compaction cannot keep up. Stall foreground write.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":18},"height":null,"hideTimeOverride":false,"id":11,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Too Many Barriers","metric":"","query":"all_barrier_nums{job=~\"$job\",instance=~\"$node\"} >= bool 200","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > bool 0 + sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recovery Triggered","metric":"","query":"sum(rate(recovery_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > bool 0 + sum(recovery_failure_cnt{job=~\"$job\",instance=~\"$node\"}) > bool 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100) + ((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Version","metric":"","query":"((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_checkpoint_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100) + ((storage_current_version_id{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_version_id{job=~\"$job\",instance=~\"$node\"}) >= bool 100)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"((storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}) >= bool 6553600000 unless + storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"} == 0)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Epoch","metric":"","query":"((storage_max_committed_epoch{job=~\"$job\",instance=~\"$node\"} - storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"}) >= bool 6553600000 unless + storage_min_pinned_epoch{job=~\"$job\",instance=~\"$node\"} == 0)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(label_replace(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}, 'L0', 'L0', 'level_index', '.*_L0') unless storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (L0) >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Compaction","metric":"","query":"sum(label_replace(storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}, 'L0', 'L0', 'level_index', '.*_L0') unless storage_level_sst_num{job=~\"$job\",instance=~\"$node\"}) by (L0) >= bool 200","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"} >= bool 200","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lagging Vacuum","metric":"","query":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"} >= bool 200","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_meta_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Meta Cache Memory","metric":"","query":"state_store_meta_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_block_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Block Cache Memory","metric":"","query":"state_store_block_cache_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 1.1","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"state_store_uploading_memory_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 0.7","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Abnormal Uploading Memory Usage","metric":"","query":"state_store_uploading_memory_usage_ratio{job=~\"$job\",instance=~\"$node\"} >= bool 0.7","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"} > bool 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Write Stall","metric":"","query":"storage_write_stop_compaction_groups{job=~\"$job\",instance=~\"$node\"} > bool 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Alerts","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Errors in the system group by type","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":26},"height":null,"hideTimeOverride":false,"id":12,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{executor_name}} (fragment_id={{fragment_id}})","metric":"","query":"sum(user_compute_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, executor_name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{source_name}} (source_id={{source_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_source_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, source_id, source_name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{error_type}} @ {{sink_name}} (sink_id={{sink_id}} fragment_id={{fragment_id}})","metric":"","query":"sum(user_sink_error{job=~\"$job\",instance=~\"$node\"}) by (error_type, sink_id, sink_name, fragment_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"source_status_is_up{job=~\"$job\",instance=~\"$node\"} == 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source error: source_id={{source_id}}, source_name={{source_name}} @ {{instance}}","metric":"","query":"source_status_is_up{job=~\"$job\",instance=~\"$node\"} == 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"remote storage error {{type}}: {{job}} @ {{instance}}","metric":"","query":"sum(rate(object_store_failure_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance, job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Errors","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Qps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":26},"height":null,"hideTimeOverride":false,"id":13,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Local mode","metric":"","query":"rate(frontend_query_counter_local_execution{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distributed mode","metric":"","query":"rate(distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Query QPS","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of each type of RisingWave components alive.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":34},"height":null,"hideTimeOverride":false,"id":14,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{worker_type}}","metric":"","query":"sum(worker_num{job=~\"$job\",instance=~\"$node\"}) by (worker_type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Count","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of active sessions in frontend nodes","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":34},"height":null,"hideTimeOverride":false,"id":15,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"","metric":"","query":"frontend_active_sessions{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Active Sessions","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":42},"height":null,"hideTimeOverride":false,"id":16,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The CPU usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":17,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"sum(rate(process_cpu_seconds_total{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Usage","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Number of CPU cores per RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":18,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{instance}}","metric":"","query":"avg(process_cpu_core_num{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node CPU Core Number","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"CPU","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":43},"height":null,"hideTimeOverride":false,"id":19,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The memory usage of each RisingWave component.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":20,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{job}} @ {{instance}}","metric":"","query":"avg(process_resident_memory_bytes{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Node Memory","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":21,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":[],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage @ {{instance}}","metric":"","query":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (instance) + sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Total)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":22,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(actor_memory_usage[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"streaming actor - {{actor_id}}","metric":"","query":"rate(actor_memory_usage[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage meta cache - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_meta_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage block cache - {{job}} @ {{instance}}","metric":"","query":"sum(state_store_block_cache_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"storage write buffer - {{job}} @ {{instance}}","metric":"","query":"sum(uploading_memory_size{job=~\"$job\",instance=~\"$node\"}) by (job,instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info) by (materialized_view_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized_view {{materialized_view_id}}","metric":"","query":"sum(stream_memory_usage{job=~\"$job\",instance=~\"$node\"} * on(table_id) group_left(materialized_view_id) table_info) by (materialized_view_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Memory Usage (Detailed)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Executor cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":23,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - cache miss - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Join - total lookups - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Group top n appendonly - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - cache miss - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Lookup executor - total lookups - table {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - cache miss - table_id {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Temporal join - total lookups - table_id {{table_id}} actor {{actor_id}}","metric":"","query":"rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - cache hit count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","query":"rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Materialize - total cache count - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","query":"rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":24,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id)) >=0 ","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"join executor cache miss ratio - - {{side}} side, join_table_id {{join_table_id}} degree_table_id {{degree_table_id}} actor {{actor_id}}","metric":"","query":"(sum(rate(stream_join_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id) ) / (sum(rate(stream_join_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (side, join_table_id, degree_table_id, actor_id)) >=0 ","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0 ","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_agg_lookup_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_lookup_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0 ","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Distinct agg cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_agg_distinct_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_agg_distinct_total_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream group top n appendonly cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_group_top_n_appendonly_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_group_top_n_appendonly_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream lookup cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_lookup_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_lookup_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Stream temporal join cache miss ratio - table {{table_id}} actor {{actor_id}} ","metric":"","query":"(sum(rate(stream_temporal_join_cache_miss_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_temporal_join_total_query_cache_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialize executor cache miss ratio - table {{table_id}} - actor {{actor_id}} {{instance}}","metric":"","query":"1 - (sum(rate(stream_materialize_cache_hit_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id) ) / (sum(rate(stream_materialize_cache_total_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (table_id, actor_id)) >=0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Executor Cache Miss Ratio","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":25,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"memory cache - {{table_id}} @ {{type}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, instance, table_id, type)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, type)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"total_meta_miss_count - {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_sst_store_block_request_counts{type='meta_miss',job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job, type)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Cache","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage bloom filter statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":24},"height":null,"hideTimeOverride":false,"id":26,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_check_bloom_filter_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter total - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_read_req_check_bloom_filter_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(state_store_read_req_positive_but_non_exist_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"bloom filter false positive - {{table_id}} @ {{job}} @ {{instance}}","metric":"","query":"sum(rate(state_store_read_req_positive_but_non_exist_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job,instance,table_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Bloom Filer","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Storage file cache statistics","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"ops"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":24},"height":null,"hideTimeOverride":false,"id":27,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache {{op}} @ {{instance}}","metric":"","query":"sum(rate(file_cache_latency_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (op, instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(file_cache_miss{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"file cache miss @ {{instance}}","metric":"","query":"sum(rate(file_cache_miss{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage File Cache","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Memory","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":44},"height":null,"hideTimeOverride":false,"id":28,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Send/Recv throughput per node for streaming exchange","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":29,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Send @ {{instance}}","metric":"","query":"sum(rate(stream_exchange_frag_send_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Recv @ {{instance}}","metric":"","query":"sum(rate(stream_exchange_frag_recv_size{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Streming Remote Exchange (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput per node","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":30,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{instance}}","metric":"","query":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{instance}}","metric":"","query":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (instance)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"row"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":31,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{query_id}} : {{source_stage_id}}.{{source_task_id}} -> {{target_stage_id}}.{{target_task_id}}","metric":"","query":"batch_exchange_recv_row_number{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Batch Exchange Recv (Rows/s)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Network","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":45},"height":null,"hideTimeOverride":false,"id":32,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":33,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_size{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The storage size of each materialized view","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"kbytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":34,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_materialized_view_stats{metric='materialized_view_total_size',job=~\"$job\",instance=~\"$node\"}/1024","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{metric}}, mv id - {{table_id}} ","metric":"","query":"storage_materialized_view_stats{metric='materialized_view_total_size',job=~\"$job\",instance=~\"$node\"}/1024","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Size","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"\n Objects are classified into 3 groups:\n - not referenced by versions: these object are being deleted from object store.\n - referenced by non-current versions: these objects are stale (not in the latest version), but those old versions may still be in use (e.g. long-running pinning). Thus those objects cannot be deleted at the moment.\n - referenced by current version: these objects are in the latest version.\n ","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":35,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"not referenced by versions","metric":"","query":"storage_stale_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by non-current versions","metric":"","query":"storage_old_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"referenced by current version","metric":"","query":"storage_current_version_object_count{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Object Total Number","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The number of bytes that have been written by compaction.Flush refers to the process of compacting Memtables to SSTables at Level 0.Compaction refers to the process of compacting SSTables at one level to another level.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":36,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Compaction - {{job}}","metric":"","query":"sum(storage_level_compact_write{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Flush - {{job}}","metric":"","query":"sum(compactor_write_build_l0_bytes{job=~\"$job\",instance=~\"$node\"}) by (job) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Write Bytes","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The remote storage read/write throughput","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"Bps"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":37,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"read - {{job}}","metric":"","query":"sum(rate(object_store_read_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"write - {{job}}","metric":"","query":"sum(rate(object_store_write_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (job)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Storage Remote I/O (Bytes/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Size statistics for checkpoint","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"bytes"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":38,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}}","metric":"","query":"histogram_quantile(0.5, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}}","metric":"","query":"histogram_quantile(0.99, sum(rate(state_store_sync_size_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum by(le, job) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"avg - {{job}}","metric":"","query":"sum by(le, job) (rate(state_store_sync_size_sum{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) / sum by(le, job) (rate(state_store_sync_size_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) > 0","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Checkpoint Size","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Storage","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":46},"height":null,"hideTimeOverride":false,"id":39,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":40,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_name}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_source_output_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of bytes read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"MB/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":41,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"(sum by (source_id)(rate(partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_id}}","metric":"","query":"(sum by (source_id)(rate(partition_input_bytes{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])))/(1000*1000)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Throughput(MB/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows read by each source per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":42,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"{{source_id}} {{source_name}} (fragment {{fragment_id}})","metric":"","query":"sum(rate(stream_source_backfill_rows_counts{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (source_id, source_name, fragment_id)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Source Backfill Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"The figure shows the number of rows written into each materialized executor actor per second.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":43,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"sum(rate(stream_executor_row_count{executor_identity=~\".*MaterializeExecutor.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(actor_id) group_left(materialized_view_id, table_name) (group(table_info{table_type=~\"MATERIALIZED_VIEW\",job=~\"$job\",instance=~\"$node\"}) by (actor_id, materialized_view_id, table_name))) by (materialized_view_id, table_name)","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"materialized view {{table_name}} table_id {{materialized_view_id}}","metric":"","query":"sum(rate(stream_executor_row_count{executor_identity=~\".*MaterializeExecutor.*\",job=~\"$job\",instance=~\"$node\"}[$__rate_interval]) * on(actor_id) group_left(materialized_view_id, table_name) (group(table_info{table_type=~\"MATERIALIZED_VIEW\",job=~\"$job\",instance=~\"$node\"}) by (actor_id, materialized_view_id, table_name))) by (materialized_view_id, table_name)","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Materialized View Throughput(rows/s)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"Total number of rows that have been read from the backfill operator used by MV on MV","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":44,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_snapshot_read_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Read Snapshot - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_snapshot_read_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(stream_backfill_upstream_output_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"Upstream - table_id={{table_id}} actor={{actor_id}} @ {{instance}}","metric":"","query":"rate(stream_backfill_upstream_output_row_count{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Backfill Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"We first record the total blocking duration(ns) of output buffer of each actor. It shows how much time it takes an actor to process a message, i.e. a barrier, a watermark or rows of data, on average. Then we divide this duration by 1 second and show it as a percentage.","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"percentunit"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":16},"height":null,"hideTimeOverride":false,"id":45,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"fragment {{fragment_id}}->{{downstream_fragment_id}}","metric":"","query":"avg(rate(stream_actor_output_buffer_blocking_duration_ns{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (fragment_id, downstream_fragment_id) / 1000000000","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Actor Output Blocking Time Ratio (Backpressure)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Streaming","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":47},"height":null,"hideTimeOverride":false,"id":46,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":47,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of running query in distributed execution mode","metric":"","query":"distributed_running_query_num{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Running query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":48,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of rejected query in distributed execution mode","metric":"","query":"distributed_rejected_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Rejected query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":""},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":8},"height":null,"hideTimeOverride":false,"id":49,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["last"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"The number of completed query in distributed execution mode","metric":"","query":"distributed_completed_query_counter{job=~\"$job\",instance=~\"$node\"}","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Completed query in distributed execution mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":8},"height":null,"hideTimeOverride":false,"id":50,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(distributed_query_latency_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Distributed Execution Mode","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":16},"height":null,"hideTimeOverride":false,"id":51,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p50 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.5, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p90 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.9, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"p99 - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(0.99, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""},{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"pmax - {{job}} @ {{instance}}","metric":"","query":"histogram_quantile(1.0, sum(rate(frontend_latency_local_execution_bucket{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])) by (le, job, instance))","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Query Latency in Local Execution Mode","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Batch","transformations":[],"transparent":false,"type":"row"},{"cacheTimeout":null,"collapsed":true,"datasource":null,"description":null,"editable":true,"error":false,"fieldConfig":{"defaults":{"thresholds":{"mode":"absolute","steps":[]}}},"gridPos":{"h":1,"w":24,"x":0,"y":48},"height":null,"hideTimeOverride":false,"id":52,"interval":null,"links":[],"maxDataPoints":100,"maxPerRow":null,"minSpan":null,"panels":[{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":0},"height":null,"hideTimeOverride":false,"id":53,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"source={{source_type}} @ {{source_id}}","metric":"","query":"rate(connector_source_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Source Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"},{"cacheTimeout":null,"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"description":"","editable":true,"error":false,"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisLabel":"","axisPlacement":"auto","axisSoftMax":null,"axisSoftMin":null,"barAlignment":0,"drawStyle":"line","fillOpacity":10,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"lineInterpolation":"linear","lineWidth":1,"pointSize":5,"scaleDistribution":{"log":2,"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{},"thresholdsStyle":{"mode":"off"}},"decimals":null,"mappings":[],"max":null,"min":null,"thresholds":{"mode":"absolute","steps":[]},"unit":"rows/s"},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":0},"height":null,"hideTimeOverride":false,"id":54,"interval":"1s","links":[],"maxDataPoints":1000,"maxPerRow":null,"minSpan":null,"options":{"legend":{"calcs":["mean"],"displayMode":"table","placement":"bottom"},"tooltip":{"mode":"single","sort":"none"}},"repeat":null,"repeatDirection":null,"span":null,"targets":[{"datasource":{"type":"prometheus","uid":"risedev-prometheus"},"expr":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","format":"time_series","hide":false,"instant":false,"interval":"","intervalFactor":2,"legendFormat":"sink={{connector_type}} @ {{sink_id}}","metric":"","query":"rate(connector_sink_rows_received{job=~\"$job\",instance=~\"$node\"}[$__rate_interval])","refId":"","step":10,"target":""}],"timeFrom":null,"timeShift":null,"title":"Connector Sink Throughput(rows)","transformations":[],"transparent":false,"type":"timeseries"}],"repeat":null,"repeatDirection":null,"span":null,"targets":[],"timeFrom":null,"timeShift":null,"title":"Connector Node","transformations":[],"transparent":false,"type":"row"}],"refresh":"","rows":[],"schemaVersion":12,"sharedCrosshair":true,"style":"dark","tags":["risingwave"],"templating":{"list":[{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, instance)","description":"Reporting instance of the metric","hide":0,"includeAll":true,"label":"Node","multi":true,"name":"node","options":[],"query":{"query":"label_values(process_cpu_seconds_total, instance)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"},{"current":{"selected":false,"text":"All","value":"__all"},"definition":"label_values(process_cpu_seconds_total, job)","description":"Reporting job of the metric","hide":0,"includeAll":true,"label":"Job","multi":true,"name":"job","options":[],"query":{"query":"label_values(process_cpu_seconds_total, job)","refId":"StandardVariableQuery"},"refresh":2,"regex":"","skipUrlSync":false,"sort":6,"type":"query"}]},"time":{"from":"now-30m","to":"now"},"timepicker":{"hidden":false,"nowDelay":null,"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"browser","title":"risingwave_dashboard","uid":"Fcy3uV1nz","version":0} diff --git a/integration_tests/ad-click/docker-compose.yml b/integration_tests/ad-click/docker-compose.yml index f1a2bbc8419cb..2b84bb00d9950 100644 --- a/integration_tests/ad-click/docker-compose.yml +++ b/integration_tests/ad-click/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/ad-ctr/docker-compose.yml b/integration_tests/ad-ctr/docker-compose.yml index fb297a8fec8ab..2bec6c35295b8 100644 --- a/integration_tests/ad-ctr/docker-compose.yml +++ b/integration_tests/ad-ctr/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/big-query-sink/bq_prepare.sql b/integration_tests/big-query-sink/bq_prepare.sql index 8ba4d670bfc61..82035c834686a 100644 --- a/integration_tests/big-query-sink/bq_prepare.sql +++ b/integration_tests/big-query-sink/bq_prepare.sql @@ -4,3 +4,35 @@ CREATE TABLE `rwctest.bqtest.bq_sink` ( target_id STRING, event_timestamp TIMESTAMP ); + +DROP TABLE IF EXISTS `rwctest.bqtest.bq_sink_data_types`; +CREATE TABLE `rwctest.bqtest.bq_sink_data_types` ( + types_id INT64, + c_boolean BOOL, + c_smallint INT64, + c_integer INT64, + c_bigint INT64, + c_decimal NUMERIC, + c_double_precision FLOAT64, + c_varchar STRING, + c_bytea BYTES, + c_date DATE, + c_timestamp DATETIME, + c_timestamptz TIMESTAMP, + c_interval INTERVAL, + c_jsonb JSON, + c_boolean_array ARRAY, + c_smallint_array ARRAY, + c_integer_array ARRAY, + c_bigint_array ARRAY, + c_decimal_array ARRAY, + c_double_precision_array ARRAY, + c_varchar_array ARRAY, + c_bytea_array ARRAY, + c_date_array ARRAY, + c_timestamp_array ARRAY, + c_timestamptz_array ARRAY, + c_interval_array ARRAY, + c_jsonb_array ARRAY, + c_struct STRUCT +); diff --git a/integration_tests/big-query-sink/create_sink.sql b/integration_tests/big-query-sink/create_sink.sql index 3b8ed3b3ef9d8..a41fe0243120d 100644 --- a/integration_tests/big-query-sink/create_sink.sql +++ b/integration_tests/big-query-sink/create_sink.sql @@ -27,3 +27,15 @@ FROM -- region = '${aws_region}', -- force_append_only='true', -- ); + +CREATE SINK bq_sink_data_types_sink +FROM + bq_sink_data_types WITH ( + connector = 'bigquery', + type = 'append-only', + bigquery.local.path= '/gcp-rwctest.json', + bigquery.project= 'rwctest', + bigquery.dataset= 'bqtest', + bigquery.table= 'bq_sink_data_types', + force_append_only='true' +); diff --git a/integration_tests/big-query-sink/create_source.sql b/integration_tests/big-query-sink/create_source.sql index bfc49aee69ce4..526ad45e59d54 100644 --- a/integration_tests/big-query-sink/create_source.sql +++ b/integration_tests/big-query-sink/create_source.sql @@ -14,3 +14,41 @@ CREATE table user_behaviors ( fields.user_id.end = '100', datagen.rows.per.second = '10' ) FORMAT PLAIN ENCODE JSON; + +CREATE TABLE bq_sink_data_types ( + types_id int, + c_boolean boolean, + c_smallint smallint, + c_integer integer, + c_bigint bigint, + c_decimal decimal, + c_double_precision double precision, + c_varchar varchar, + c_bytea bytea, + c_date date, + c_timestamp timestamp, + c_timestamptz timestamptz, + c_interval interval, + c_jsonb jsonb, + c_boolean_array boolean[], + c_smallint_array smallint[], + c_integer_array integer[], + c_bigint_array bigint[], + c_decimal_array decimal[], + c_double_precision_array double precision[], + c_varchar_array varchar[], + c_bytea_array bytea[], + c_date_array date[], + c_timestamp_array timestamp[], + c_timestamptz_array timestamptz[], + c_interval_array interval[], + c_jsonb_array jsonb[], + c_struct STRUCT, + PRIMARY KEY(types_id) +); + +INSERT INTO bq_sink_data_types VALUES (1, False, 0, 0, 0, 0, 0, '', '\x00', '0001-01-01', '0001-01-01 00:00:00', '0001-01-01 00:00:00'::timestamptz, interval '0 second', '{}', array[]::boolean[], array[]::smallint[], array[]::integer[], array[]::bigint[], array[]::decimal[], array[]::double precision[], array[]::varchar[], array[]::bytea[], array[]::date[], array[]::timestamp[], array[]::timestamptz[], array[]::interval[], array[]::jsonb[], ROW(1, False)); + +INSERT INTO bq_sink_data_types VALUES (2, False, -32767, -2147483647, -9223372036854775807, -10.0, -10000.0, 'aa', '\x00', '1970-01-01', '1970-01-01 00:00:00.123456', '1970-01-01 00:00:00.123456Z', interval '10 second', '{}', array[False::boolean]::boolean[], array[-32767::smallint]::smallint[], array[-2147483647::integer]::integer[], array[-9223372036854775807::bigint]::bigint[], array[-10.0::decimal]::decimal[], array[-10000.0::double precision]::double precision[], array[''::varchar]::varchar[], array['\x00'::bytea]::bytea[], array['1970-01-01'::date]::date[], array['1970-01-01 00:00:00'::timestamp]::timestamp[], array['1970-01-01 00:00:00Z'::timestamptz]::timestamptz[], array[interval '0 second'::interval]::interval[], array['{}'::jsonb]::jsonb[], ROW(2, True)); + +INSERT INTO bq_sink_data_types VALUES (3, True, 32767, 2147483647, 9223372036854775807, -10.0, 10000.0, 'zzzzzz', '\xffffff', '9999-12-31', '9999-12-31 23:59:59', '9999-12-31 23:59:59Z', interval '9990 year', '{"whatever":"meaningless"}', array[True::boolean]::boolean[], array[32767::smallint]::smallint[], array[2147483647::integer]::integer[], array[9223372036854775807::bigint]::bigint[], array[-10.0::decimal]::decimal[], array[10000.0::double precision]::double precision[], array['zzzzzz'::varchar]::varchar[], array['\xffffff'::bytea]::bytea[], array['9999-12-31'::date]::date[], array['9999-12-31 23:59:59'::timestamp]::timestamp[], array['9999-12-31 23:59:59Z'::timestamptz]::timestamptz[], array[interval '9990 year'::interval]::interval[], array['{"whatever":"meaningless"}'::jsonb]::jsonb[], ROW(3, True)); diff --git a/integration_tests/big-query-sink/docker-compose.yml b/integration_tests/big-query-sink/docker-compose.yml index ca16ca8f90b3b..279f43a43b217 100644 --- a/integration_tests/big-query-sink/docker-compose.yml +++ b/integration_tests/big-query-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: risingwave-standalone: extends: @@ -7,10 +6,10 @@ services: service: risingwave-standalone volumes: - "../../gcp-rwctest.json:/gcp-rwctest.json" - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -32,7 +31,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/big-query-sink/sink_check.py b/integration_tests/big-query-sink/sink_check.py index 7388c853c11cd..f669f0336f597 100644 --- a/integration_tests/big-query-sink/sink_check.py +++ b/integration_tests/big-query-sink/sink_check.py @@ -2,7 +2,7 @@ import subprocess import sys -relations = ['rwctest.bqtest.bq_sink'] +relations = ['rwctest.bqtest.bq_sink', 'rwctest.bqtest.bq_sink_data_types'] failed_cases = [] for rel in relations: diff --git a/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml b/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml index 425e086c56ba0..9f09b203ef700 100644 --- a/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml +++ b/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: cassandra: image: cassandra:4.0 @@ -22,10 +21,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -45,7 +44,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/cdn-metrics/docker-compose.yml b/integration_tests/cdn-metrics/docker-compose.yml index 69eb63a448eb2..05d3d786e6279 100644 --- a/integration_tests/cdn-metrics/docker-compose.yml +++ b/integration_tests/cdn-metrics/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/citus-cdc/docker-compose.yml b/integration_tests/citus-cdc/docker-compose.yml index d77e0e80dbfa9..8afb665e02cd1 100644 --- a/integration_tests/citus-cdc/docker-compose.yml +++ b/integration_tests/citus-cdc/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -90,7 +89,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/clickhouse-sink/docker-compose.yml b/integration_tests/clickhouse-sink/docker-compose.yml index 76b0f7fe607f5..beb2ee1254739 100644 --- a/integration_tests/clickhouse-sink/docker-compose.yml +++ b/integration_tests/clickhouse-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: clickhouse-server: image: clickhouse/clickhouse-server:23.3.8.21-alpine @@ -17,10 +16,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -36,7 +35,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/clickstream/docker-compose.yml b/integration_tests/clickstream/docker-compose.yml index faf66b770af7d..4015a3a976ced 100644 --- a/integration_tests/clickstream/docker-compose.yml +++ b/integration_tests/clickstream/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/client-library/client_test.py b/integration_tests/client-library/client_test.py index a970ace57bbcf..f5621b46b0648 100644 --- a/integration_tests/client-library/client_test.py +++ b/integration_tests/client-library/client_test.py @@ -4,42 +4,45 @@ def check_go(): - subprocess.run(["docker", "compose", "exec", "go-lang", "bash", "-c", "cd /go-client && ./run.sh"], check=True) + subprocess.run(["docker", "compose", "exec", "go-lang", "bash", "-c", "cd /go-client && ./run.sh"], + cwd="integration_tests/client-library", check=True) def check_python(): - subprocess.run(["docker", "compose", "exec", "python", "bash", "-c", - "cd /python-client && pip3 install -r requirements.txt && pytest"], check=True) + subprocess.run(["docker", "compose", "exec", "python", "bash", "-c", "cd /python-client && pip3 install -r requirements.txt && pytest"], + cwd="integration_tests/client-library", check=True) def check_java(): subprocess.run(["docker", "compose", "exec", "java", "bash", "-c", "apt-get update && apt-get install -y maven"], - check=True) - subprocess.run(["docker", "compose", "exec", "java", "bash", "-c", "cd /java-client && mvn clean test"], check=True) + cwd="integration_tests/client-library", check=True) + subprocess.run(["docker", "compose", "exec", "java", "bash", "-c", "cd /java-client && mvn clean test"], + cwd="integration_tests/client-library", check=True) def check_nodejs(): subprocess.run( ["docker", "compose", "exec", "nodejs", "bash", "-c", "cd /nodejs-client && npm install && npm test"], - check=True) + cwd="integration_tests/client-library", check=True) def check_php(): subprocess.run( ["docker", "compose", "exec", "php", "bash", "-c", "cd /php-client && phpunit tests/RWClientTest.php"], - check=True) + cwd="integration_tests/client-library", check=True) def check_ruby(): subprocess.run(["docker", "compose", "exec", "ruby", "bash", "-c", "cd /ruby-client && ruby test/crud_test.rb"], - check=True) + cwd="integration_tests/client-library", check=True) def check_spring_boot(): - subprocess.run(["docker", "compose", "exec", "spring-boot", "bash", "-c", - "cd /spring-boot-client && mvn spring-boot:run"], check=True) + subprocess.run(["docker", "compose", "exec", "spring-boot", "bash", "-c", "cd /spring-boot-client && mvn spring-boot:run"], + cwd="integration_tests/client-library", check=True) -subprocess.run(["docker", "compose", "up", "-d"], check=True) +subprocess.run(["docker", "compose", "up", "-d"], + cwd="integration_tests/client-library", check=True) sleep(10) failed_cases = [] @@ -68,6 +71,3 @@ def check_spring_boot(): if len(failed_cases) != 0: print(f"--- client check failed for case\n{failed_cases}") sys.exit(1) - -print("--- docker compose down") -subprocess.run(["docker", "compose", "down", "--remove-orphans", "-v"], check=True) diff --git a/integration_tests/client-library/csharp/csharp.csproj b/integration_tests/client-library/csharp/csharp.csproj index 2d7ae2b7e2f48..f57b4028aabe9 100644 --- a/integration_tests/client-library/csharp/csharp.csproj +++ b/integration_tests/client-library/csharp/csharp.csproj @@ -12,7 +12,7 @@ - + diff --git a/integration_tests/client-library/docker-compose.yml b/integration_tests/client-library/docker-compose.yml index d2e4663fdc378..c8a03d353b18e 100644 --- a/integration_tests/client-library/docker-compose.yml +++ b/integration_tests/client-library/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -63,7 +62,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/cockroach-sink/docker-compose.yml b/integration_tests/cockroach-sink/docker-compose.yml index a205dca9e19cf..d325c57865baf 100644 --- a/integration_tests/cockroach-sink/docker-compose.yml +++ b/integration_tests/cockroach-sink/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -38,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/datagen/go.mod b/integration_tests/datagen/go.mod index 5056ca6e82848..e9bb8cda78a8d 100644 --- a/integration_tests/datagen/go.mod +++ b/integration_tests/datagen/go.mod @@ -70,12 +70,12 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/stretchr/testify v1.8.0 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect - golang.org/x/net v0.17.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/integration_tests/datagen/go.sum b/integration_tests/datagen/go.sum index a35ed04f01a33..b65f55e0e9854 100644 --- a/integration_tests/datagen/go.sum +++ b/integration_tests/datagen/go.sum @@ -455,8 +455,8 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -538,8 +538,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -626,12 +626,12 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/integration_tests/debezium-mongo/docker-compose.yaml b/integration_tests/debezium-mongo/docker-compose.yaml index 4ae90ea22eb94..886e4622ad6ac 100644 --- a/integration_tests/debezium-mongo/docker-compose.yaml +++ b/integration_tests/debezium-mongo/docker-compose.yaml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml diff --git a/integration_tests/debezium-mysql/docker-compose.yml b/integration_tests/debezium-mysql/docker-compose.yml index 77ff7689b78f8..6cb577ac23886 100644 --- a/integration_tests/debezium-mysql/docker-compose.yml +++ b/integration_tests/debezium-mysql/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -78,7 +77,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/debezium-postgres/docker-compose.yml b/integration_tests/debezium-postgres/docker-compose.yml index ef9094e432f6f..327cb44d6db7c 100644 --- a/integration_tests/debezium-postgres/docker-compose.yml +++ b/integration_tests/debezium-postgres/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -88,7 +87,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/debezium-sqlserver/docker-compose.yml b/integration_tests/debezium-sqlserver/docker-compose.yml index 848d2dc5393ab..9d4bbbf0a5bb6 100644 --- a/integration_tests/debezium-sqlserver/docker-compose.yml +++ b/integration_tests/debezium-sqlserver/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -76,7 +75,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/deltalake-sink/docker-compose.yml b/integration_tests/deltalake-sink/docker-compose.yml index a486e2336b502..2a799f9fcf45b 100644 --- a/integration_tests/deltalake-sink/docker-compose.yml +++ b/integration_tests/deltalake-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: spark: image: apache/spark:3.3.1 @@ -13,10 +12,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -32,7 +31,7 @@ services: volumes: compute-node-0: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/doris-sink/docker-compose.yml b/integration_tests/doris-sink/docker-compose.yml index fc7cfd751e989..4b43632f51319 100644 --- a/integration_tests/doris-sink/docker-compose.yml +++ b/integration_tests/doris-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: fe: platform: linux/amd64 @@ -35,10 +34,10 @@ services: networks: mynetwork: ipv4_address: 172.21.0.4 - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: mynetwork: ipv4_address: 172.21.0.5 @@ -86,7 +85,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/dynamodb/create_sink.sql b/integration_tests/dynamodb/create_sink.sql new file mode 100644 index 0000000000000..6de71404a9da1 --- /dev/null +++ b/integration_tests/dynamodb/create_sink.sql @@ -0,0 +1,13 @@ +CREATE SINK dyn_sink +FROM + movies +WITH +( + connector = 'dynamodb', + table = 'Movies', + primary_key = 'year,title', + endpoint = 'http://dynamodb:8000', + region = 'us', + access_key = 'ac', + secret_key = 'sk' +); diff --git a/integration_tests/dynamodb/create_source.sql b/integration_tests/dynamodb/create_source.sql new file mode 100644 index 0000000000000..24cd3ed52e3fd --- /dev/null +++ b/integration_tests/dynamodb/create_source.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS movies ( + year integer, + title varchar, + description varchar, + primary key (year, title) +); diff --git a/integration_tests/dynamodb/create_table_request.json b/integration_tests/dynamodb/create_table_request.json new file mode 100644 index 0000000000000..b10747e1116d5 --- /dev/null +++ b/integration_tests/dynamodb/create_table_request.json @@ -0,0 +1,15 @@ +{ + "TableName": "Movies", + "KeySchema": [ + { "AttributeName": "year", "KeyType": "HASH" }, + { "AttributeName": "title", "KeyType": "RANGE" } + ], + "AttributeDefinitions": [ + { "AttributeName": "year", "AttributeType": "N" }, + { "AttributeName": "title", "AttributeType": "S" } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } +} diff --git a/integration_tests/dynamodb/data_check b/integration_tests/dynamodb/data_check new file mode 100644 index 0000000000000..991948c9a7971 --- /dev/null +++ b/integration_tests/dynamodb/data_check @@ -0,0 +1 @@ +movies diff --git a/integration_tests/dynamodb/docker-compose.yml b/integration_tests/dynamodb/docker-compose.yml new file mode 100644 index 0000000000000..0bc6a50849976 --- /dev/null +++ b/integration_tests/dynamodb/docker-compose.yml @@ -0,0 +1,46 @@ +--- +version: "3" +services: + dynamodb: + image: amazon/dynamodb-local + ports: + - "8000:8000" + command: "-jar DynamoDBLocal.jar -sharedDb -inMemory -port 8000" + risingwave-standalone: + extends: + file: ../../docker/docker-compose.yml + service: risingwave-standalone + postgres-0: + extends: + file: ../../docker/docker-compose.yml + service: postgres-0 + grafana-0: + extends: + file: ../../docker/docker-compose.yml + service: grafana-0 + minio-0: + extends: + file: ../../docker/docker-compose.yml + service: minio-0 + prometheus-0: + extends: + file: ../../docker/docker-compose.yml + service: prometheus-0 + message_queue: + extends: + file: ../../docker/docker-compose.yml + service: message_queue +volumes: + risingwave-standalone: + external: false + postgres-0: + external: false + grafana-0: + external: false + minio-0: + external: false + prometheus-0: + external: false + message_queue: + external: false +name: risingwave-compose diff --git a/integration_tests/dynamodb/prepare.sh b/integration_tests/dynamodb/prepare.sh new file mode 100644 index 0000000000000..29a8009b1635a --- /dev/null +++ b/integration_tests/dynamodb/prepare.sh @@ -0,0 +1,8 @@ +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +curl -X POST http://localhost:8000/ \ + -H "Accept-Encoding: identity" \ + -H "X-Amz-Target: DynamoDB_20120810.CreateTable" \ + -H "Content-Type: application/x-amz-json-1.0" \ + -H "Authorization: AWS4-HMAC-SHA256 Credential=DUMMY/20240601/us/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=DUMMY" \ + --data "@$SCRIPT_DIR/create_table_request.json" diff --git a/integration_tests/dynamodb/query.sql b/integration_tests/dynamodb/query.sql new file mode 100644 index 0000000000000..8ae514743ae97 --- /dev/null +++ b/integration_tests/dynamodb/query.sql @@ -0,0 +1,42 @@ +INSERT INTO + movies (year, title, description) +VALUES + (2020, 'The Emoji Movie', 'a'), + (2019, 'Avengers: Endgame', 'b'), + (2018, 'Black Panther', 'c'), + (2017, 'Wonder Woman', 'd'); + +INSERT INTO + movies (year, title, description) +VALUES + (2023, 'Beautiful beauty', 'ABC DUMMY'); + +FLUSH; + +DELETE FROM + movies +WHERE + title = 'Beautiful beauty'; + +DELETE FROM + movies +WHERE + year = 2017; + +FLUSH; + +INSERT INTO + movies (year, title, description) +VALUES + (2017, 'Beautiful beauty', 'ABC DUMMY'); + +FLUSH; + +UPDATE + movies +SET + description = 'ABC' +WHERE + year = 2017; + +FLUSH; diff --git a/integration_tests/dynamodb/sink_check.py b/integration_tests/dynamodb/sink_check.py new file mode 100644 index 0000000000000..2bfb9b6fe1352 --- /dev/null +++ b/integration_tests/dynamodb/sink_check.py @@ -0,0 +1,48 @@ +import sys +import subprocess +import json + +curl = """ +curl -X POST http://localhost:8000/ \ + -H "Accept-Encoding: identity" \ + -H "X-Amz-Target: DynamoDB_20120810.Scan" \ + -H "Content-Type: application/x-amz-json-1.0" \ + -H "Authorization: AWS4-HMAC-SHA256 Credential=DUMMY/20240601/us/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=DUMMY" \ + --data '{"TableName": "Movies"}' +""" + +output = subprocess.Popen( + ["bash", "-c", curl,], + stdout=subprocess.PIPE, +) +msgs = json.loads(output.stdout.read()) +print(msgs) + +item_count = msgs["Count"] +items = msgs["Items"] +print("item count: ", item_count) +print("items: ", items) + +output.stdout.close() +output.wait() + +if item_count != 4: + print("Data check failed") + sys.exit(1) + +for item in items: + title = item["title"]["S"] + year = item["year"]["N"] + description = item["description"]["S"] + + if title not in ["Black Panther", "Avengers: Endgame", "Beautiful beauty", "The Emoji Movie"]: + print(f"Data check failed: unexcept title: {title}") + sys.exit(1) + + if int(year) not in range(2017, 2021): + print(f"Data check failed: unexcept year: {year}") + sys.exit(1) + + if description not in ["a", "b", "c", "ABC"]: + print(f"Data check failed: unexcept description: {description}") + sys.exit(1) diff --git a/integration_tests/elasticsearch-sink/docker-compose.yml b/integration_tests/elasticsearch-sink/docker-compose.yml index 195e8d0070eb8..097de4beb5490 100644 --- a/integration_tests/elasticsearch-sink/docker-compose.yml +++ b/integration_tests/elasticsearch-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: elasticsearch7: image: docker.elastic.co/elasticsearch/elasticsearch:7.11.0 @@ -29,10 +28,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -48,7 +47,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/feature-store/docker-compose.yml b/integration_tests/feature-store/docker-compose.yml index b1767f8d04864..77de22a8c3522 100644 --- a/integration_tests/feature-store/docker-compose.yml +++ b/integration_tests/feature-store/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: kafka: image: confluentinc/cp-kafka:7.1.0 @@ -73,10 +72,10 @@ services: [kafka,meta-node-0,frontend-node-0] volumes: - ".log:/opt/feature-store/.log" - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose-distributed.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose-distributed.yml @@ -98,7 +97,7 @@ services: file: ../../docker/docker-compose-distributed.yml service: prometheus-0 volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/http-sink/docker-compose.yml b/integration_tests/http-sink/docker-compose.yml index 8fba5ff352dc0..9a7c42b1443e0 100644 --- a/integration_tests/http-sink/docker-compose.yml +++ b/integration_tests/http-sink/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -24,7 +23,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/iceberg-cdc/docker-compose.yml b/integration_tests/iceberg-cdc/docker-compose.yml index e483003907746..703571c2f10a5 100644 --- a/integration_tests/iceberg-cdc/docker-compose.yml +++ b/integration_tests/iceberg-cdc/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -127,7 +127,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/iceberg-sink/docker-compose.yml b/integration_tests/iceberg-sink/docker-compose.yml index c7182b873d85b..84bda01b21ceb 100644 --- a/integration_tests/iceberg-sink/docker-compose.yml +++ b/integration_tests/iceberg-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" x-airflow-common: &airflow-common image: apache/airflow:2.6.2-python3.10 @@ -53,10 +52,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -161,7 +160,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/hive/config.ini b/integration_tests/iceberg-sink2/docker/hive/config.ini index d644f3c0d46a1..55a03c3f94753 100644 --- a/integration_tests/iceberg-sink2/docker/hive/config.ini +++ b/integration_tests/iceberg-sink2/docker/hive/config.ini @@ -10,7 +10,7 @@ type=append-only force_append_only = true catalog.type = hive catalog.uri = thrift://metastore:9083 -warehouse.path = s3://icebergdata/demo +warehouse.path = s3a://icebergdata/demo s3.endpoint=http://minio-0:9301 s3.access.key = hummockadmin s3.secret.key = hummockadmin diff --git a/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml b/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml index 3314083c1077b..449d6043e6e95 100644 --- a/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml @@ -75,10 +75,10 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: @@ -106,7 +106,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml b/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml index 3f2bb75479563..714fcdf6e0b95 100644 --- a/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml @@ -76,17 +76,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml b/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml index 025db74c23cae..ee3e1da9b62da 100644 --- a/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml @@ -80,17 +80,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/storage/config.ini b/integration_tests/iceberg-sink2/docker/storage/config.ini index 13e912b8fc3b8..b4e8263f23092 100644 --- a/integration_tests/iceberg-sink2/docker/storage/config.ini +++ b/integration_tests/iceberg-sink2/docker/storage/config.ini @@ -14,6 +14,6 @@ s3.secret.key = hummockadmin s3.region = ap-southeast-1 catalog.type = storage catalog.name = demo -warehouse.path = s3://icebergdata/demo +warehouse.path = s3a://icebergdata/demo database.name=s1 table.name=t1 \ No newline at end of file diff --git a/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml b/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml index 8d6d6f72d53c5..4e0ec11eff500 100644 --- a/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml @@ -56,17 +56,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/hive/config.ini b/integration_tests/iceberg-source/docker/hive/config.ini index df07c75258257..80ce27ab14fdd 100644 --- a/integration_tests/iceberg-source/docker/hive/config.ini +++ b/integration_tests/iceberg-source/docker/hive/config.ini @@ -8,7 +8,7 @@ port=4566 connector = iceberg catalog.type = hive catalog.uri = thrift://metastore:9083 -warehouse.path = s3://icebergdata/demo +warehouse.path = s3a://icebergdata/demo s3.endpoint=http://minio-0:9301 s3.access.key = hummockadmin s3.secret.key = hummockadmin diff --git a/integration_tests/iceberg-source/docker/hive/docker-compose.yml b/integration_tests/iceberg-source/docker/hive/docker-compose.yml index 3314083c1077b..449d6043e6e95 100644 --- a/integration_tests/iceberg-source/docker/hive/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/hive/docker-compose.yml @@ -75,10 +75,10 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: @@ -106,7 +106,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml b/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml index 3f2bb75479563..714fcdf6e0b95 100644 --- a/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml @@ -76,17 +76,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/rest/docker-compose.yml b/integration_tests/iceberg-source/docker/rest/docker-compose.yml index 025db74c23cae..ee3e1da9b62da 100644 --- a/integration_tests/iceberg-source/docker/rest/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/rest/docker-compose.yml @@ -80,17 +80,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/storage/config.ini b/integration_tests/iceberg-source/docker/storage/config.ini index dd795fd3ef684..6439453dcaae6 100644 --- a/integration_tests/iceberg-source/docker/storage/config.ini +++ b/integration_tests/iceberg-source/docker/storage/config.ini @@ -11,6 +11,6 @@ s3.access.key = hummockadmin s3.secret.key = hummockadmin s3.region = ap-southeast-1 catalog.type = storage -warehouse.path = s3://icebergdata/demo +warehouse.path = s3a://icebergdata/demo database.name=s1 table.name=t1 \ No newline at end of file diff --git a/integration_tests/iceberg-source/docker/storage/docker-compose.yml b/integration_tests/iceberg-source/docker/storage/docker-compose.yml index 8d6d6f72d53c5..4e0ec11eff500 100644 --- a/integration_tests/iceberg-source/docker/storage/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/storage/docker-compose.yml @@ -56,17 +56,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/kafka-cdc-sink/docker-compose.yml b/integration_tests/kafka-cdc-sink/docker-compose.yml index c8c31d7762cf6..1cebe9b73f284 100644 --- a/integration_tests/kafka-cdc-sink/docker-compose.yml +++ b/integration_tests/kafka-cdc-sink/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -133,7 +132,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/kafka-cdc/docker-compose.yml b/integration_tests/kafka-cdc/docker-compose.yml index d62fa2acd9df3..6eaa5b5ead7ab 100644 --- a/integration_tests/kafka-cdc/docker-compose.yml +++ b/integration_tests/kafka-cdc/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -38,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/kinesis-s3-source/data_check b/integration_tests/kinesis-s3-source/data_check index 9708f5cb617c3..b95837955a510 100644 --- a/integration_tests/kinesis-s3-source/data_check +++ b/integration_tests/kinesis-s3-source/data_check @@ -1 +1 @@ -ad_impression,ad_click,ad_ctr,ad_ctr_5min \ No newline at end of file +ad_ctr,ad_ctr_5min \ No newline at end of file diff --git a/integration_tests/kinesis-s3-source/docker-compose.yml b/integration_tests/kinesis-s3-source/docker-compose.yml index 9108537309bd7..74dabde96f7ba 100644 --- a/integration_tests/kinesis-s3-source/docker-compose.yml +++ b/integration_tests/kinesis-s3-source/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -50,7 +49,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/livestream/docker-compose.yml b/integration_tests/livestream/docker-compose.yml index 1f3e0736a4def..e263b704bc90d 100644 --- a/integration_tests/livestream/docker-compose.yml +++ b/integration_tests/livestream/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mindsdb/docker-compose.yml b/integration_tests/mindsdb/docker-compose.yml index c6eeec5c75ee6..0cd82b10a6529 100644 --- a/integration_tests/mindsdb/docker-compose.yml +++ b/integration_tests/mindsdb/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -52,7 +51,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mongodb-cdc/docker-compose.yaml b/integration_tests/mongodb-cdc/docker-compose.yaml index 60d477945b38b..de09a204d991b 100644 --- a/integration_tests/mongodb-cdc/docker-compose.yaml +++ b/integration_tests/mongodb-cdc/docker-compose.yaml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mongodb/docker-compose.yaml b/integration_tests/mongodb/docker-compose.yaml index 59ac89215ec14..a2855c200e6b0 100644 --- a/integration_tests/mongodb/docker-compose.yaml +++ b/integration_tests/mongodb/docker-compose.yaml @@ -1,4 +1,3 @@ -version: "3" services: mongodb: image: mongo:4.4 diff --git a/integration_tests/mqtt/docker-compose.yml b/integration_tests/mqtt/docker-compose.yml index 9db7e7c04f8fc..b91ddd482509c 100644 --- a/integration_tests/mqtt/docker-compose.yml +++ b/integration_tests/mqtt/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: risingwave-standalone: extends: @@ -13,10 +12,10 @@ services: - echo "running command"; printf 'allow_anonymous true\nlistener 1883 0.0.0.0' > /mosquitto/config/mosquitto.conf; echo "starting service..."; cat /mosquitto/config/mosquitto.conf;/docker-entrypoint.sh;/usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf ports: - 1883:1883 - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -36,7 +35,7 @@ services: volumes: compute-node-0: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mysql-cdc/docker-compose.yml b/integration_tests/mysql-cdc/docker-compose.yml index 5cd3003669d7b..b2779c42c05b6 100644 --- a/integration_tests/mysql-cdc/docker-compose.yml +++ b/integration_tests/mysql-cdc/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -70,7 +69,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mysql-sink/docker-compose.yml b/integration_tests/mysql-sink/docker-compose.yml index 97d3d78ce4cb0..8f8c4f9aa4336 100644 --- a/integration_tests/mysql-sink/docker-compose.yml +++ b/integration_tests/mysql-sink/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -41,7 +40,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/nats/docker-compose.yml b/integration_tests/nats/docker-compose.yml index 2010ec2d8b4ed..930f1a719fd7f 100644 --- a/integration_tests/nats/docker-compose.yml +++ b/integration_tests/nats/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: risingwave-standalone: extends: @@ -10,10 +9,10 @@ services: ports: - "4222:4222" command: -js - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -50,7 +49,7 @@ services: volumes: compute-node-0: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/pinot-sink/docker-compose.yml b/integration_tests/pinot-sink/docker-compose.yml index 222c1d7e39735..c7d08dcc005e9 100644 --- a/integration_tests/pinot-sink/docker-compose.yml +++ b/integration_tests/pinot-sink/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -84,7 +83,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/postgres-cdc/docker-compose.yml b/integration_tests/postgres-cdc/docker-compose.yml index 1898d20a8c9ae..333ee2f4080c3 100644 --- a/integration_tests/postgres-cdc/docker-compose.yml +++ b/integration_tests/postgres-cdc/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -30,7 +29,7 @@ services: - POSTGRES_PASSWORD=123456 - POSTGRES_DB=mydb ports: - - 8432:5432 + - 5432:5432 healthcheck: test: [ "CMD-SHELL", "pg_isready --username=myuser --dbname=mydb" ] interval: 5s @@ -78,7 +77,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/postgres-sink/docker-compose.yml b/integration_tests/postgres-sink/docker-compose.yml index e443965c2e5be..6f5a16db64c24 100644 --- a/integration_tests/postgres-sink/docker-compose.yml +++ b/integration_tests/postgres-sink/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -44,7 +43,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/presto-trino/docker-compose.yml b/integration_tests/presto-trino/docker-compose.yml index b12785139ebad..5de9dc34eb78a 100644 --- a/integration_tests/presto-trino/docker-compose.yml +++ b/integration_tests/presto-trino/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -48,7 +47,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/prometheus/docker-compose.yml b/integration_tests/prometheus/docker-compose.yml index 0beb75839d4cd..cd840807ea1ac 100644 --- a/integration_tests/prometheus/docker-compose.yml +++ b/integration_tests/prometheus/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -67,7 +66,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/prometheus/prometheus.yaml b/integration_tests/prometheus/prometheus.yaml index c70ddd434064e..7d955a835b32d 100644 --- a/integration_tests/prometheus/prometheus.yaml +++ b/integration_tests/prometheus/prometheus.yaml @@ -27,7 +27,7 @@ scrape_configs: - job_name: etcd static_configs: - - targets: ["etcd-0:2379"] + - targets: ["postgres-0:2379"] - job_name: redpanda static_configs: diff --git a/integration_tests/pubsub/create_sink.sql b/integration_tests/pubsub/create_sink.sql new file mode 100644 index 0000000000000..82016040c3cbf --- /dev/null +++ b/integration_tests/pubsub/create_sink.sql @@ -0,0 +1,14 @@ +CREATE SINK pubsub_sink +FROM + personnel +WITH +( + connector = 'google_pubsub', + pubsub.endpoint = 'pubsub-emulator:8900', + pubsub.emulator_host = 'pubsub-emulator:8900', + pubsub.project_id = 'demo', + pubsub.topic = 'test', +) FORMAT PLAIN ENCODE JSON ( + force_append_only='true', +); + diff --git a/integration_tests/pubsub/create_source.sql b/integration_tests/pubsub/create_source.sql new file mode 100644 index 0000000000000..5856abfabbf47 --- /dev/null +++ b/integration_tests/pubsub/create_source.sql @@ -0,0 +1 @@ +CREATE TABLE IF NOT EXISTS personnel (id integer, name varchar); diff --git a/integration_tests/pubsub/data_check b/integration_tests/pubsub/data_check new file mode 100644 index 0000000000000..0528e08f7601a --- /dev/null +++ b/integration_tests/pubsub/data_check @@ -0,0 +1 @@ +personnel diff --git a/integration_tests/pubsub/docker-compose.yml b/integration_tests/pubsub/docker-compose.yml new file mode 100644 index 0000000000000..e04def6dd88cd --- /dev/null +++ b/integration_tests/pubsub/docker-compose.yml @@ -0,0 +1,47 @@ +--- +version: "3" +services: + pubsub-emulator: + image: google/cloud-sdk:latest + command: > + gcloud beta emulators pubsub start --project=demo --host-port=0.0.0.0:8900 + ports: + - "8900:8900" + risingwave-standalone: + extends: + file: ../../docker/docker-compose.yml + service: risingwave-standalone + postgres-0: + extends: + file: ../../docker/docker-compose.yml + service: postgres-0 + grafana-0: + extends: + file: ../../docker/docker-compose.yml + service: grafana-0 + minio-0: + extends: + file: ../../docker/docker-compose.yml + service: minio-0 + prometheus-0: + extends: + file: ../../docker/docker-compose.yml + service: prometheus-0 + message_queue: + extends: + file: ../../docker/docker-compose.yml + service: message_queue +volumes: + risingwave-standalone: + external: false + postgres-0: + external: false + grafana-0: + external: false + minio-0: + external: false + prometheus-0: + external: false + message_queue: + external: false +name: risingwave-compose diff --git a/integration_tests/pubsub/prepare.sh b/integration_tests/pubsub/prepare.sh new file mode 100644 index 0000000000000..eabf514e151ca --- /dev/null +++ b/integration_tests/pubsub/prepare.sh @@ -0,0 +1,9 @@ +export PUBSUB_EMULATOR_HOST=localhost:8900 + +curl -X PUT http://localhost:8900/v1/projects/demo/topics/test +curl -X PUT http://localhost:8900/v1/projects/demo/subscriptions/sub \ + -H 'content-type: application/json' \ + --data '{"topic":"projects/demo/topics/test"}' + +curl -X GET http://localhost:8900/v1/projects/demo/topics +curl -X GET http://localhost:8900/v1/projects/demo/subscriptions diff --git a/integration_tests/pubsub/query.sql b/integration_tests/pubsub/query.sql new file mode 100644 index 0000000000000..124ce9133634e --- /dev/null +++ b/integration_tests/pubsub/query.sql @@ -0,0 +1,13 @@ +INSERT INTO + personnel +VALUES + (1, 'Alice'), + (2, 'Bob'), + (3, 'Tom'), + (4, 'Jerry'), + (5, 'Araminta'), + (6, 'Clover'), + (7, 'Posey'), + (8, 'Waverly'); + +FLUSH; diff --git a/integration_tests/pubsub/sink_check.py b/integration_tests/pubsub/sink_check.py new file mode 100644 index 0000000000000..3957d99817542 --- /dev/null +++ b/integration_tests/pubsub/sink_check.py @@ -0,0 +1,26 @@ +import sys +import subprocess +import json + +curl = """ +PUBSUB_EMULATOR_HOST=localhost:8900 curl -s -X POST 'http://localhost:8900/v1/projects/demo/subscriptions/sub:pull' \ + -H 'content-type: application/json' \ + --data '{"maxMessages":10}' +""" + +output = subprocess.Popen( + ["bash", "-c", curl,], + stdout=subprocess.PIPE, +) +msgs = json.loads(output.stdout.read()) +print(msgs) + +msg_count = len(msgs["receivedMessages"]) +print("msg count", msg_count) + +output.stdout.close() +output.wait() + +if msg_count != 8: + print("Data check failed") + sys.exit(1) diff --git a/integration_tests/redis-sink/docker-compose.yml b/integration_tests/redis-sink/docker-compose.yml index 0fd33048a29bf..8f3c3eb9cdd85 100644 --- a/integration_tests/redis-sink/docker-compose.yml +++ b/integration_tests/redis-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: redis: image: 'redis:latest' @@ -16,10 +15,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -39,7 +38,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/schema-registry/docker-compose.yml b/integration_tests/schema-registry/docker-compose.yml index 3020a96790fbf..2209fb7e20aee 100644 --- a/integration_tests/schema-registry/docker-compose.yml +++ b/integration_tests/schema-registry/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -62,7 +61,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/snowflake-sink/README.md b/integration_tests/snowflake-sink/README.md index 98bcf73bcb265..8d38921985b5d 100644 --- a/integration_tests/snowflake-sink/README.md +++ b/integration_tests/snowflake-sink/README.md @@ -43,3 +43,27 @@ launch your risingwave cluster, and execute the following sql commands respectiv - `create_sink.sql` note: the column name(s) in your materialized view should be exactly the same as the ones in your pre-defined snowflake table, due to what we specified for snowflake pipe previously in `snowflake_prep.sql`. + +## 3. Sink data into snowflake with UPSERT + +1. To begin the process of sink data into Snowflake with upsert, we need to set up snowflake and s3 as we did for step 1 + +2. Execute the following sql commands respectively. + - `upsert/create_source.sql` + - `upsert/create_mv.sql` + - `upsert/create_sink.sql` + + After execution, we will import RisingWave's data change log into the snowflake's table. + +3. We then use the following sql statement to create the dynamic table. We can select it to get the result of the upsert + ``` + CREATE OR REPLACE DYNAMIC TABLE user_behaviors + TARGET_LAG = '1 minute' + WAREHOUSE = test_warehouse + AS SELECT * + FROM ( + SELECT *, ROW_NUMBER() OVER (PARTITION BY {primary_key} ORDER BY __row_id DESC) AS dedupe_id + FROM t3 + ) AS subquery + WHERE dedupe_id = 1 AND (__op = 1 or __op = 3) + ``` \ No newline at end of file diff --git a/integration_tests/snowflake-sink/upsert/create_mv.sql b/integration_tests/snowflake-sink/upsert/create_mv.sql new file mode 100644 index 0000000000000..c40ebe4caa252 --- /dev/null +++ b/integration_tests/snowflake-sink/upsert/create_mv.sql @@ -0,0 +1,13 @@ +-- please note that the column name(s) for your mv should be *exactly* +-- the same as the column name(s) in your snowflake table, since we are matching column by name. + +CREATE MATERIALIZED VIEW ss_mv AS +WITH sub AS changelog FROM user_behaviors +SELECT + user_id, + target_id, + event_timestamp AT TIME ZONE 'America/Indiana/Indianapolis' as event_timestamp, + changelog_op AS __op, + _changelog_row_id::bigint AS __row_id +FROM + sub; \ No newline at end of file diff --git a/integration_tests/snowflake-sink/upsert/create_sink.sql b/integration_tests/snowflake-sink/upsert/create_sink.sql new file mode 100644 index 0000000000000..20f45968439db --- /dev/null +++ b/integration_tests/snowflake-sink/upsert/create_sink.sql @@ -0,0 +1,16 @@ +CREATE SINK snowflake_sink FROM ss_mv WITH ( + connector = 'snowflake', + type = 'append-only', + snowflake.database = 'EXAMPLE_DB', + snowflake.schema = 'EXAMPLE_SCHEMA', + snowflake.pipe = 'EXAMPLE_SNOWFLAKE_PIPE', + snowflake.account_identifier = '-', + snowflake.user = 'XZHSEH', + snowflake.rsa_public_key_fp = 'EXAMPLE_FP', + snowflake.private_key = 'EXAMPLE_PK', + snowflake.s3_bucket = 'EXAMPLE_S3_BUCKET', + snowflake.aws_access_key_id = 'EXAMPLE_AWS_ID', + snowflake.aws_secret_access_key = 'EXAMPLE_SECRET_KEY', + snowflake.aws_region = 'EXAMPLE_REGION', + snowflake.s3_path = 'EXAMPLE_S3_PATH', +); \ No newline at end of file diff --git a/integration_tests/snowflake-sink/upsert/create_source.sql b/integration_tests/snowflake-sink/upsert/create_source.sql new file mode 100644 index 0000000000000..0a5bc60f49922 --- /dev/null +++ b/integration_tests/snowflake-sink/upsert/create_source.sql @@ -0,0 +1,19 @@ +-- please note that this will create a source that generates 1,000 rows in 10 seconds +-- you may want to change the configuration for better testing / demo purpose + +CREATE table user_behaviors ( + user_id int, + target_id VARCHAR, + target_type VARCHAR, + event_timestamp TIMESTAMPTZ, + behavior_type VARCHAR, + parent_target_type VARCHAR, + parent_target_id VARCHAR, + PRIMARY KEY(user_id) +) WITH ( + connector = 'datagen', + fields.user_id.kind = 'sequence', + fields.user_id.start = '1', + fields.user_id.end = '1000', + datagen.rows.per.second = '100' +) FORMAT PLAIN ENCODE JSON; diff --git a/integration_tests/sqlserver-sink/README.md b/integration_tests/sqlserver-sink/README.md new file mode 100644 index 0000000000000..7006b55ff861a --- /dev/null +++ b/integration_tests/sqlserver-sink/README.md @@ -0,0 +1,86 @@ +# Demo: Sinking to Microsoft SQL Server + +In this demo, we want to showcase how RisingWave is able to sink data to Microsoft SQL Server. + + +1. Launch the cluster: + +```sh +docker-compose up -d +``` + +The cluster contains a RisingWave cluster and its necessary dependencies, a datagen that generates the data, a SQL Server instance for sink. + +2. Create the SQL Server table: + +```sh +docker exec -it sqlserver-server /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P SomeTestOnly@SA -Q " +CREATE DATABASE SinkTest; +GO +USE SinkTest; +GO +CREATE TABLE t_many_data_type ( + k1 int, k2 int, + c_boolean bit, + c_int16 smallint, + c_int32 int, + c_int64 bigint, + c_float32 float, + c_float64 float, + c_decimal decimal, + c_date date, + c_time time, + c_timestamp datetime2, + c_nvarchar nvarchar(1024), + c_varbinary varbinary(1024), + PRIMARY KEY (k1,k2) +); +GO" +``` + +3. Create the RisingWave table and sink: + +```sh +docker exec -it postgres-0 psql -h 127.0.0.1 -p 4566 -d dev -U root -c " +CREATE TABLE t_many_data_type_rw ( + k1 int, k2 int, + c_int16 smallint, + c_int32 int, + c_int64 bigint, + c_float32 float, + c_float64 double, + c_timestamp timestamp, + c_nvarchar string +) WITH ( + connector = 'datagen', + datagen.split.num = '1', + datagen.rows.per.second = '100', + fields.k1.kind = 'random', + fields.k1.min = '0', + fields.k1.max = '10000', + fields.k2.kind = 'random', + fields.k2.min = '0', + fields.k2.max = '10000' +); + +CREATE SINK s_many_data_type FROM t_many_data_type_rw WITH ( + connector = 'sqlserver', + type = 'upsert', + sqlserver.host = 'localhost', + sqlserver.port = 1433, + sqlserver.user = 'SA', + sqlserver.password = 'SomeTestOnly@SA', + sqlserver.database = 'SinkTest', + sqlserver.table = 't_many_data_type', + primary_key = 'k1,k2', +); +" +``` + +4. Verify the result in SQL Server, for example: + +```sh +docker exec -it sqlserver-server /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P SomeTestOnly@SA -Q " +SELECT count(*) FROM SinkTest.dbo.t_many_data_type; +" +``` diff --git a/integration_tests/sqlserver-sink/docker-compose.yml b/integration_tests/sqlserver-sink/docker-compose.yml new file mode 100644 index 0000000000000..ec31f7d47e12e --- /dev/null +++ b/integration_tests/sqlserver-sink/docker-compose.yml @@ -0,0 +1,46 @@ +--- +services: + sqlserver-server: + container_name: sqlserver-server + image: mcr.microsoft.com/mssql/server:2022-latest + hostname: sqlserver-server + ports: + - 1433:1433 + environment: + ACCEPT_EULA: 'Y' + SA_PASSWORD: 'SomeTestOnly@SA' + risingwave-standalone: + extends: + file: ../../docker/docker-compose.yml + service: risingwave-standalone + postgres-0: + container_name: postgres-0 + extends: + file: ../../docker/docker-compose.yml + service: postgres-0 + grafana-0: + extends: + file: ../../docker/docker-compose.yml + service: grafana-0 + minio-0: + extends: + file: ../../docker/docker-compose.yml + service: minio-0 + prometheus-0: + extends: + file: ../../docker/docker-compose.yml + service: prometheus-0 +volumes: + risingwave-standalone: + external: false + postgres-0: + external: false + grafana-0: + external: false + minio-0: + external: false + prometheus-0: + external: false + message_queue: + external: false +name: risingwave-compose diff --git a/integration_tests/starrocks-sink/docker-compose.yml b/integration_tests/starrocks-sink/docker-compose.yml index 81ef7c277dad0..e3a06cc33587a 100644 --- a/integration_tests/starrocks-sink/docker-compose.yml +++ b/integration_tests/starrocks-sink/docker-compose.yml @@ -1,5 +1,4 @@ --- -version: "3" services: starrocks-fe: image: starrocks/fe-ubuntu:3.1.7 @@ -37,10 +36,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -62,7 +61,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/superset/docker-compose.yml b/integration_tests/superset/docker-compose.yml index 271e79755aab3..3d7b9ed2494ca 100644 --- a/integration_tests/superset/docker-compose.yml +++ b/integration_tests/superset/docker-compose.yml @@ -9,16 +9,15 @@ x-superset-volumes: - ./docker:/app/docker - superset_home:/app/superset_home -version: "3.7" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -112,7 +111,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/tidb-cdc-sink/docker-compose.yml b/integration_tests/tidb-cdc-sink/docker-compose.yml index 9177936d47ab8..f1b4cb0ebdd7b 100644 --- a/integration_tests/tidb-cdc-sink/docker-compose.yml +++ b/integration_tests/tidb-cdc-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -204,7 +204,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/twitter-pulsar/docker-compose.yml b/integration_tests/twitter-pulsar/docker-compose.yml index 8061425d48d38..a3e91c9f8751a 100644 --- a/integration_tests/twitter-pulsar/docker-compose.yml +++ b/integration_tests/twitter-pulsar/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -42,7 +41,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/twitter/docker-compose.yml b/integration_tests/twitter/docker-compose.yml index e5637b7f2664f..e59e71b3839ce 100644 --- a/integration_tests/twitter/docker-compose.yml +++ b/integration_tests/twitter/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/upsert-avro/docker-compose.yml b/integration_tests/upsert-avro/docker-compose.yml index 695c8f8fcb043..9176ca053ba4e 100644 --- a/integration_tests/upsert-avro/docker-compose.yml +++ b/integration_tests/upsert-avro/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -43,7 +42,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false @@ -53,4 +52,4 @@ volumes: external: false message_queue: external: false -name: risingwave-compose \ No newline at end of file +name: risingwave-compose diff --git a/integration_tests/vector/docker-compose.yml b/integration_tests/vector/docker-compose.yml index 2179cd66542c4..13101925ac4a3 100644 --- a/integration_tests/vector/docker-compose.yml +++ b/integration_tests/vector/docker-compose.yml @@ -1,14 +1,13 @@ --- -version: "3" services: risingwave-standalone: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose-distributed.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose-distributed.yml @@ -34,7 +33,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/java/connector-node/connector-api/src/main/java/com/risingwave/connector/api/Monitor.java b/java/connector-node/connector-api/src/main/java/com/risingwave/connector/api/Monitor.java new file mode 100644 index 0000000000000..5b89cfeb6e5df --- /dev/null +++ b/java/connector-node/connector-api/src/main/java/com/risingwave/connector/api/Monitor.java @@ -0,0 +1,33 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ + +package com.risingwave.connector.api; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; + +public class Monitor { + + public static String dumpStackTrace() { + StringBuilder builder = new StringBuilder(); + ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); + for (ThreadInfo ti : threadMxBean.dumpAllThreads(true, true)) { + builder.append(ti.toString()); + } + return builder.toString(); + } +} diff --git a/java/connector-node/connector-api/src/main/java/com/risingwave/connector/api/TableSchema.java b/java/connector-node/connector-api/src/main/java/com/risingwave/connector/api/TableSchema.java index ded49e003d19a..c7e9f7035f1e7 100644 --- a/java/connector-node/connector-api/src/main/java/com/risingwave/connector/api/TableSchema.java +++ b/java/connector-node/connector-api/src/main/java/com/risingwave/connector/api/TableSchema.java @@ -20,12 +20,18 @@ import com.risingwave.proto.Data.DataType.TypeName; import com.risingwave.proto.PlanCommon; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TableSchema { + + static final Logger LOG = LoggerFactory.getLogger(TableSchema.class); + private final List columnNames; private final Map columns; private final Map columnIndices; @@ -80,16 +86,20 @@ public Object getFromRow(String columnName, SinkRow row) { } public static TableSchema fromProto(ConnectorServiceProto.TableSchema tableSchema) { - return new TableSchema( - tableSchema.getColumnsList().stream() - .map(PlanCommon.ColumnDesc::getName) - .collect(Collectors.toList()), - tableSchema.getColumnsList().stream() - .map(PlanCommon.ColumnDesc::getColumnType) - .collect(Collectors.toList()), - tableSchema.getPkIndicesList().stream() - .map(i -> tableSchema.getColumns(i).getName()) - .collect(Collectors.toList())); + // filter out additional columns + var instance = + new TableSchema( + tableSchema.getColumnsList().stream() + .map(PlanCommon.ColumnDesc::getName) + .collect(Collectors.toList()), + tableSchema.getColumnsList().stream() + .map(PlanCommon.ColumnDesc::getColumnType) + .collect(Collectors.toList()), + tableSchema.getPkIndicesList().stream() + .map(i -> tableSchema.getColumns(i).getName()) + .collect(Collectors.toList())); + LOG.info("table column names: {}", Arrays.toString(instance.getColumnNames())); + return instance; } public List getPrimaryKeys() { diff --git a/java/connector-node/python-client/.gitignore b/java/connector-node/python-client/.gitignore index 600d2d33badf4..536c32383d754 100644 --- a/java/connector-node/python-client/.gitignore +++ b/java/connector-node/python-client/.gitignore @@ -1 +1,2 @@ -.vscode \ No newline at end of file +.vscode +sink-client-venv/ diff --git a/java/connector-node/python-client/integration_tests.py b/java/connector-node/python-client/integration_tests.py index b16b5eaf34ad4..909859afc218a 100644 --- a/java/connector-node/python-client/integration_tests.py +++ b/java/connector-node/python-client/integration_tests.py @@ -117,7 +117,7 @@ def load_stream_chunk_payload(input_file): return payloads -def test_sink(prop, format, payload_input, table_schema, is_coordinated=False): +def test_sink(prop, payload_input, table_schema, is_coordinated=False): sink_param = connector_service_pb2.SinkParam( sink_id=0, properties=prop, @@ -128,7 +128,6 @@ def test_sink(prop, format, payload_input, table_schema, is_coordinated=False): request_list = [ connector_service_pb2.SinkWriterStreamRequest( start=connector_service_pb2.SinkWriterStreamRequest.StartSink( - format=format, sink_param=sink_param, ) ) @@ -291,9 +290,6 @@ def test_stream_chunk_data_format(param): parser.add_argument( "--deltalake_sink", action="store_true", help="run deltalake sink test" ) - parser.add_argument( - "--input_file", default="./data/sink_input.json", help="input data to run tests" - ) parser.add_argument( "--input_binary_file", default="./data/sink_input", @@ -302,29 +298,18 @@ def test_stream_chunk_data_format(param): parser.add_argument( "--es_sink", action="store_true", help="run elasticsearch sink test" ) - parser.add_argument( - "--data_format_use_json", default=True, help="choose json or streamchunk" - ) args = parser.parse_args() - use_json = args.data_format_use_json == True or args.data_format_use_json == "True" - if use_json: - payload = load_json_payload(args.input_file) - format = connector_service_pb2.SinkPayloadFormat.JSON - else: - payload = load_stream_chunk_payload(args.input_binary_file) - format = connector_service_pb2.SinkPayloadFormat.STREAM_CHUNK + payload = load_stream_chunk_payload(args.input_binary_file) # stream chunk format if args.stream_chunk_format_test: param = { - "format": format, "payload_input": payload, "table_schema": make_mock_schema_stream_chunk(), } test_stream_chunk_data_format(param) param = { - "format": format, "payload_input": payload, "table_schema": make_mock_schema(), } @@ -337,7 +322,5 @@ def test_stream_chunk_data_format(param): test_deltalake_sink(param) if args.es_sink: test_elasticsearch_sink(param) - - # json format if args.upsert_iceberg_sink: test_upsert_iceberg_sink(param) diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkCoordinatorHandler.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkCoordinatorHandler.java index b9d8d2fe228d9..8e811278e4971 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkCoordinatorHandler.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkCoordinatorHandler.java @@ -23,9 +23,6 @@ public class JniSinkCoordinatorHandler { private static final Logger LOG = LoggerFactory.getLogger(JniSinkCoordinatorHandler.class); public static void runJniSinkCoordinatorThread(long requestRxPtr, long responseTxPtr) { - // For jni.rs - java.lang.Thread.currentThread() - .setContextClassLoader(java.lang.ClassLoader.getSystemClassLoader()); JniSinkCoordinatorResponseObserver responseObserver = new JniSinkCoordinatorResponseObserver(responseTxPtr); SinkCoordinatorStreamObserver sinkCoordinatorStreamObserver = diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkValidationHandler.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkValidationHandler.java index 759c1925f4f02..415284f1daf3c 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkValidationHandler.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkValidationHandler.java @@ -32,11 +32,6 @@ public static byte[] validate(byte[] validateSinkRequestBytes) try { var request = ConnectorServiceProto.ValidateSinkRequest.parseFrom(validateSinkRequestBytes); - - // For jni.rs - java.lang.Thread.currentThread() - .setContextClassLoader(java.lang.ClassLoader.getSystemClassLoader()); - ConnectorServiceProto.SinkParam sinkParam = request.getSinkParam(); TableSchema tableSchema = TableSchema.fromProto(sinkParam.getTableSchema()); String connectorName = getConnectorName(request.getSinkParam()); diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkWriterHandler.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkWriterHandler.java index 1d87db9537cfa..89bd1dd7e6865 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkWriterHandler.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JniSinkWriterHandler.java @@ -23,9 +23,6 @@ public class JniSinkWriterHandler { private static final Logger LOG = LoggerFactory.getLogger(JniSinkWriterHandler.class); public static void runJniSinkWriterThread(long requestRxPtr, long responseTxPtr) { - // For jni.rs - java.lang.Thread.currentThread() - .setContextClassLoader(java.lang.ClassLoader.getSystemClassLoader()); JniSinkWriterResponseObserver responseObserver = new JniSinkWriterResponseObserver(responseTxPtr); SinkWriterStreamObserver sinkWriterStreamObserver = diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JsonDeserializer.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JsonDeserializer.java deleted file mode 100644 index c941b09efe95c..0000000000000 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JsonDeserializer.java +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.connector; - -import static io.grpc.Status.INVALID_ARGUMENT; - -import com.google.gson.Gson; -import com.risingwave.connector.api.TableSchema; -import com.risingwave.connector.api.sink.*; -import com.risingwave.proto.ConnectorServiceProto; -import com.risingwave.proto.ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch.JsonPayload; -import com.risingwave.proto.Data; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.util.Base64; -import java.util.Map; -import java.util.stream.Collectors; - -public class JsonDeserializer implements Deserializer { - private final TableSchema tableSchema; - - public JsonDeserializer(TableSchema tableSchema) { - this.tableSchema = tableSchema; - } - - // Encoding here should be consistent with `datum_to_json_object()` in - // src/connector/src/sink/mod.rs - @Override - public CloseableIterable deserialize( - ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch writeBatch) { - if (!writeBatch.hasJsonPayload()) { - throw INVALID_ARGUMENT - .withDescription("expected JsonPayload, got " + writeBatch.getPayloadCase()) - .asRuntimeException(); - } - JsonPayload jsonPayload = writeBatch.getJsonPayload(); - return new TrivialCloseIterable<>( - jsonPayload.getRowOpsList().stream() - .map( - rowOp -> { - Map columnValues = - new Gson().fromJson(rowOp.getLine(), Map.class); - Object[] values = new Object[columnValues.size()]; - for (String columnName : tableSchema.getColumnNames()) { - if (!columnValues.containsKey(columnName)) { - throw INVALID_ARGUMENT - .withDescription( - "column " - + columnName - + " not found in json") - .asRuntimeException(); - } - Data.DataType.TypeName typeName = - tableSchema.getColumnType(columnName); - values[tableSchema.getColumnIndex(columnName)] = - validateJsonDataTypes( - typeName, columnValues.get(columnName)); - } - return (SinkRow) new ArraySinkRow(rowOp.getOpType(), values); - }) - .collect(Collectors.toList())); - } - - private static Long castLong(Object value) { - if (value instanceof Integer) { - return ((Integer) value).longValue(); - } else if (value instanceof Double) { - double d = (Double) value; - if (d % 1.0 != 0.0) { - - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "unable to cast into long from non-integer double value: " + d) - .asRuntimeException(); - } - return ((Double) value).longValue(); - } else if (value instanceof Long) { - return (Long) value; - } else if (value instanceof Short) { - return ((Short) value).longValue(); - } else if (value instanceof Float) { - double f = (Float) value; - if (f % 1.0 != 0.0) { - - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "unable to cast into long from non-integer float value: " + f) - .asRuntimeException(); - } - return ((Float) value).longValue(); - } else { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into long from " + value.getClass()) - .asRuntimeException(); - } - } - - private static Double castDouble(Object value) { - if (value instanceof Double) { - return (Double) value; - } else if (value instanceof Float) { - return ((Float) value).doubleValue(); - } else { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into double from " + value.getClass()) - .asRuntimeException(); - } - } - - private static BigDecimal castDecimal(Object value) { - if (value instanceof String) { - // FIXME(eric): See `datum_to_json_object()` in src/connector/src/sink/mod.rs - return new BigDecimal((String) value); - } else if (value instanceof BigDecimal) { - return (BigDecimal) value; - } else { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into double from " + value.getClass()) - .asRuntimeException(); - } - } - - private static LocalTime castTime(Object value) { - try { - Long milli = castLong(value); - return LocalTime.ofNanoOfDay(milli * 1_000_000L); - } catch (RuntimeException e) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into time from " + value.getClass()) - .asRuntimeException(); - } - } - - private static LocalDate castDate(Object value) { - try { - Long days = castLong(value); - return LocalDate.ofEpochDay(days); - } catch (RuntimeException e) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into date from " + value.getClass()) - .asRuntimeException(); - } - } - - private static Object validateJsonDataTypes(Data.DataType.TypeName typeName, Object value) { - // value might be null - if (value == null) { - return null; - } - switch (typeName) { - case INT16: - return castLong(value).shortValue(); - case INT32: - return castLong(value).intValue(); - case INT64: - return castLong(value); - case VARCHAR: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected string, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case DOUBLE: - return castDouble(value); - case FLOAT: - return castDouble(value).floatValue(); - case DECIMAL: - return castDecimal(value); - case BOOLEAN: - if (!(value instanceof Boolean)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected boolean, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case TIMESTAMP: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "Expected timestamp in string, got " + value.getClass()) - .asRuntimeException(); - } - return LocalDateTime.parse((String) value); - case TIMESTAMPTZ: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "Expected timestamptz in string, got " + value.getClass()) - .asRuntimeException(); - } - return OffsetDateTime.parse((String) value); - case TIME: - return castTime(value); - case DATE: - return castDate(value); - case INTERVAL: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected interval, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case JSONB: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected jsonb, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case BYTEA: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected bytea, got " + value.getClass()) - .asRuntimeException(); - } - return Base64.getDecoder().decode((String) value); - case LIST: - if (!(value instanceof java.util.ArrayList)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected list, got " + value.getClass()) - .asRuntimeException(); - } - return ((java.util.ArrayList) value).toArray(); - default: - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unsupported type " + typeName) - .asRuntimeException(); - } - } -} diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkUtils.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkUtils.java index 679deedebcabf..73f0799e44d1d 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkUtils.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkUtils.java @@ -40,6 +40,7 @@ public static SinkFactory getSinkFactory(String sinkName) { case "jdbc": return new JDBCSinkFactory(); case "elasticsearch": + case "opensearch": return new EsSinkFactory(); case "cassandra": return new CassandraFactory(); diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java index cd61da38d6cb5..53dfe326fbd9d 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java @@ -206,19 +206,7 @@ private void bindSink(ConnectorServiceProto.SinkWriterStreamRequest.StartSink st String connectorName = getConnectorName(sinkParam); SinkFactory sinkFactory = SinkUtils.getSinkFactory(connectorName); sink = sinkFactory.createWriter(tableSchema, sinkParam.getPropertiesMap()); - switch (startSink.getFormat()) { - case FORMAT_UNSPECIFIED: - case UNRECOGNIZED: - throw INVALID_ARGUMENT - .withDescription("should specify payload format in request") - .asRuntimeException(); - case JSON: - deserializer = new JsonDeserializer(tableSchema); - break; - case STREAM_CHUNK: - deserializer = new StreamChunkDeserializer(tableSchema); - break; - } + deserializer = new StreamChunkDeserializer(tableSchema); this.connectorName = connectorName.toUpperCase(); ConnectorNodeMetrics.incActiveSinkConnections(connectorName, "node1"); } diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/JniSourceValidateHandler.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/JniSourceValidateHandler.java index 1b75cf34c8c50..6c3cc16a59352 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/JniSourceValidateHandler.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/JniSourceValidateHandler.java @@ -31,10 +31,6 @@ public static byte[] validate(byte[] validateSourceRequestBytes) var request = ConnectorServiceProto.ValidateSourceRequest.parseFrom( validateSourceRequestBytes); - - // For jni.rs - java.lang.Thread.currentThread() - .setContextClassLoader(java.lang.ClassLoader.getSystemClassLoader()); validateSource(request); // validate pass return ConnectorServiceProto.ValidateSourceResponse.newBuilder().build().toByteArray(); diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzConnectorConfig.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzConnectorConfig.java index 33808447caad9..a5804974fb29c 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzConnectorConfig.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzConnectorConfig.java @@ -31,8 +31,8 @@ public class DbzConnectorConfig { private static final Logger LOG = LoggerFactory.getLogger(DbzConnectorConfig.class); - public static final String WAIT_FOR_STREAMING_START_BEFORE_EXIT_SECS = - "cdc.source.wait.streaming.before.exit.seconds"; + private static final String WAIT_FOR_STREAMING_START_TIMEOUT_SECS = + "cdc.source.wait.streaming.start.timeout"; /* Common configs */ public static final String HOST = "hostname"; @@ -47,6 +47,7 @@ public class DbzConnectorConfig { /* MySQL configs */ public static final String MYSQL_SERVER_ID = "server.id"; + public static final String MYSQL_SSL_MODE = "ssl.mode"; /* Postgres configs */ public static final String PG_SLOT_NAME = "slot.name"; @@ -88,6 +89,7 @@ private static Map extractDebeziumProperties( private final SourceTypeE sourceType; private final Properties resolvedDbzProps; private final boolean isBackfillSource; + private final int waitStreamingStartTimeout; public long getSourceId() { return sourceId; @@ -105,6 +107,10 @@ public boolean isBackfillSource() { return isBackfillSource; } + public int getWaitStreamingStartTimeout() { + return waitStreamingStartTimeout; + } + public DbzConnectorConfig( SourceTypeE source, long sourceId, @@ -118,6 +124,9 @@ public DbzConnectorConfig( var isCdcBackfill = null != userProps.get(SNAPSHOT_MODE_KEY) && userProps.get(SNAPSHOT_MODE_KEY).equals(SNAPSHOT_MODE_BACKFILL); + var waitStreamingStartTimeout = + Integer.parseInt( + userProps.getOrDefault(WAIT_FOR_STREAMING_START_TIMEOUT_SECS, "30")); LOG.info( "DbzConnectorConfig: source={}, sourceId={}, startOffset={}, snapshotDone={}, isCdcBackfill={}, isCdcSourceJob={}", @@ -231,8 +240,8 @@ public DbzConnectorConfig( ConfigurableOffsetBackingStore.OFFSET_STATE_VALUE, startOffset); } - var mongodbUrl = userProps.get("mongodb.url"); - var collection = userProps.get("collection.name"); + var mongodbUrl = userProps.get(MongoDb.MONGO_URL); + var collection = userProps.get(MongoDb.MONGO_COLLECTION_NAME); var connectionStr = new ConnectionString(mongodbUrl); var connectorName = String.format( @@ -254,6 +263,7 @@ public DbzConnectorConfig( this.sourceType = source; this.resolvedDbzProps = dbzProps; this.isBackfillSource = isCdcBackfill; + this.waitStreamingStartTimeout = waitStreamingStartTimeout; } private Properties initiateDbConfig(String fileName, StringSubstitutor substitutor) { diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzSourceUtils.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzSourceUtils.java index a4a16f010ad63..83c6d59fac921 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzSourceUtils.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/DbzSourceUtils.java @@ -103,31 +103,30 @@ private static String quotePostgres(String identifier) { return "\"" + identifier + "\""; } - public static boolean waitForStreamingRunning(SourceTypeE sourceType, String dbServerName) { + public static boolean waitForStreamingRunning( + SourceTypeE sourceType, String dbServerName, int waitStreamingStartTimeout) { // Wait for streaming source of source that supported backfill LOG.info("Waiting for streaming source of {} to start", dbServerName); if (sourceType == SourceTypeE.MYSQL) { - return waitForStreamingRunningInner("mysql", dbServerName); + return waitForStreamingRunningInner("mysql", dbServerName, waitStreamingStartTimeout); } else if (sourceType == SourceTypeE.POSTGRES) { - return waitForStreamingRunningInner("postgres", dbServerName); + return waitForStreamingRunningInner( + "postgres", dbServerName, waitStreamingStartTimeout); } else { LOG.info("Unsupported backfill source, just return true for {}", dbServerName); return true; } } - private static boolean waitForStreamingRunningInner(String connector, String dbServerName) { - int timeoutSecs = - Integer.parseInt( - System.getProperty( - DbzConnectorConfig.WAIT_FOR_STREAMING_START_BEFORE_EXIT_SECS)); + private static boolean waitForStreamingRunningInner( + String connector, String dbServerName, int waitStreamingStartTimeout) { int pollCount = 0; while (!isStreamingRunning(connector, dbServerName, "streaming")) { - if (pollCount > timeoutSecs) { + if (pollCount > waitStreamingStartTimeout) { LOG.error( "Debezium streaming source of {} failed to start in timeout {}", dbServerName, - timeoutSecs); + waitStreamingStartTimeout); return false; } try { @@ -154,8 +153,9 @@ private static boolean isStreamingRunning(String connector, String server, Strin mbeanServer.getAttribute( getStreamingMetricsObjectName(connector, server, contextName), "Connected"); - } catch (JMException ex) { - LOG.warn("Failed to get streaming metrics", ex); + } catch (JMException _ex) { + // ignore the exception, as it is expected when the streaming source + // (aka. binlog client) is not ready } return false; } diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/MySqlValidator.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/MySqlValidator.java index c92b4dad540c9..d20a18185a74d 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/MySqlValidator.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/MySqlValidator.java @@ -20,10 +20,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.*; public class MySqlValidator extends DatabaseValidator implements AutoCloseable { private final Map userProps; @@ -51,9 +48,14 @@ public MySqlValidator( var dbName = userProps.get(DbzConnectorConfig.DB_NAME); var jdbcUrl = ValidatorUtils.getJdbcUrl(SourceTypeE.MYSQL, dbHost, dbPort, dbName); - var user = userProps.get(DbzConnectorConfig.USER); - var password = userProps.get(DbzConnectorConfig.PASSWORD); - this.jdbcConnection = DriverManager.getConnection(jdbcUrl, user, password); + var properties = new Properties(); + properties.setProperty("user", userProps.get(DbzConnectorConfig.USER)); + properties.setProperty("password", userProps.get(DbzConnectorConfig.PASSWORD)); + properties.setProperty( + "sslMode", userProps.getOrDefault(DbzConnectorConfig.MYSQL_SSL_MODE, "DISABLED")); + properties.setProperty("allowPublicKeyRetrieval", "true"); + + this.jdbcConnection = DriverManager.getConnection(jdbcUrl, properties); this.isCdcSourceJob = isCdcSourceJob; this.isBackfillTable = isBackfillTable; } @@ -185,17 +187,17 @@ private void validateTableSchema() throws SQLException { stmt.setString(1, dbName); stmt.setString(2, tableName); - // Field name in lower case -> data type - var schema = new HashMap(); + // Field name in lower case -> data type, because MySQL column name is case-insensitive + // https://dev.mysql.com/doc/refman/5.7/en/identifier-case-sensitivity.html + var upstreamSchema = new HashMap(); var pkFields = new HashSet(); var res = stmt.executeQuery(); while (res.next()) { var field = res.getString(1); var dataType = res.getString(2); var key = res.getString(3); - schema.put(field.toLowerCase(), dataType); + upstreamSchema.put(field.toLowerCase(), dataType); if (key.equalsIgnoreCase("PRI")) { - // RisingWave always use lower case for column name pkFields.add(field.toLowerCase()); } } @@ -206,7 +208,7 @@ private void validateTableSchema() throws SQLException { if (e.getKey().startsWith(ValidatorUtils.INTERNAL_COLUMN_PREFIX)) { continue; } - var dataType = schema.get(e.getKey().toLowerCase()); + var dataType = upstreamSchema.get(e.getKey().toLowerCase()); if (dataType == null) { throw ValidatorUtils.invalidArgument( "Column '" + e.getKey() + "' not found in the upstream database"); @@ -217,7 +219,7 @@ private void validateTableSchema() throws SQLException { } } - if (!ValidatorUtils.isPrimaryKeyMatch(tableSchema, pkFields)) { + if (!isPrimaryKeyMatch(tableSchema, pkFields)) { throw ValidatorUtils.invalidArgument("Primary key mismatch"); } } @@ -230,6 +232,18 @@ public void close() throws Exception { } } + private boolean isPrimaryKeyMatch(TableSchema sourceSchema, Set pkFields) { + if (sourceSchema.getPrimaryKeys().size() != pkFields.size()) { + return false; + } + for (var colName : sourceSchema.getPrimaryKeys()) { + if (!pkFields.contains(colName.toLowerCase())) { + return false; + } + } + return true; + } + private boolean isDataTypeCompatible(String mysqlDataType, Data.DataType.TypeName typeName) { int val = typeName.getNumber(); switch (mysqlDataType) { diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/PostgresValidator.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/PostgresValidator.java index d91e5f885d609..93d4fdee0bcd4 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/PostgresValidator.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/PostgresValidator.java @@ -79,14 +79,19 @@ public PostgresValidator( @Override public void validateDbConfig() { - // TODO: check database server version - try (var stmt = jdbcConnection.createStatement()) { - // check whether wal has been enabled - var res = stmt.executeQuery(ValidatorUtils.getSql("postgres.wal")); - while (res.next()) { - if (!res.getString(1).equals("logical")) { - throw ValidatorUtils.invalidArgument( - "Postgres wal_level should be 'logical'.\nPlease modify the config and restart your Postgres server."); + try { + if (jdbcConnection.getMetaData().getDatabaseMajorVersion() > 16) { + throw ValidatorUtils.failedPrecondition("Postgres version should be less than 16."); + } + + try (var stmt = jdbcConnection.createStatement()) { + // check whether wal has been enabled + var res = stmt.executeQuery(ValidatorUtils.getSql("postgres.wal")); + while (res.next()) { + if (!res.getString(1).equals("logical")) { + throw ValidatorUtils.invalidArgument( + "Postgres wal_level should be 'logical'.\nPlease modify the config and restart your Postgres server."); + } } } } catch (SQLException e) { @@ -180,11 +185,10 @@ private void validateTableSchema() throws SQLException { var pkFields = new HashSet(); while (res.next()) { var name = res.getString(1); - // RisingWave always use lower case for column name - pkFields.add(name.toLowerCase()); + pkFields.add(name); } - if (!ValidatorUtils.isPrimaryKeyMatch(tableSchema, pkFields)) { + if (!isPrimaryKeyMatch(tableSchema, pkFields)) { throw ValidatorUtils.invalidArgument("Primary key mismatch"); } } @@ -223,6 +227,19 @@ private void validateTableSchema() throws SQLException { } } + private boolean isPrimaryKeyMatch(TableSchema sourceSchema, Set pkFields) { + if (sourceSchema.getPrimaryKeys().size() != pkFields.size()) { + return false; + } + // postgres column name is case-sensitive + for (var colName : sourceSchema.getPrimaryKeys()) { + if (!pkFields.contains(colName)) { + return false; + } + } + return true; + } + private void validatePrivileges() throws SQLException { boolean isSuperUser = false; if (this.isAwsRds) { diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/ValidatorUtils.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/ValidatorUtils.java index 5c7d9ea6d4948..20d631a3267c9 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/ValidatorUtils.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/common/ValidatorUtils.java @@ -14,12 +14,10 @@ package com.risingwave.connector.source.common; -import com.risingwave.connector.api.TableSchema; import com.risingwave.connector.api.source.SourceTypeE; import io.grpc.Status; import java.io.IOException; import java.util.Properties; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,16 +70,4 @@ public static String getJdbcUrl( throw ValidatorUtils.invalidArgument("Unknown source type: " + sourceType); } } - - public static boolean isPrimaryKeyMatch(TableSchema sourceSchema, Set pkFields) { - if (sourceSchema.getPrimaryKeys().size() != pkFields.size()) { - return false; - } - for (var colName : sourceSchema.getPrimaryKeys()) { - if (!pkFields.contains(colName)) { - return false; - } - } - return true; - } } diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngine.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngine.java index b515ce8bd79b6..ed22fe36416ec 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngine.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngine.java @@ -17,6 +17,7 @@ import static io.debezium.config.CommonConnectorConfig.TOPIC_PREFIX; import static io.debezium.schema.AbstractTopicNamingStrategy.*; +import com.risingwave.connector.api.source.SourceTypeE; import com.risingwave.proto.ConnectorServiceProto; import io.debezium.embedded.Connect; import io.debezium.engine.DebeziumEngine; @@ -33,6 +34,7 @@ public class DbzCdcEngine implements Runnable { /** If config is not valid will throw exceptions */ public DbzCdcEngine( + SourceTypeE connector, long sourceId, Properties config, DebeziumEngine.CompletionCallback completionCallback) { @@ -41,6 +43,7 @@ public DbzCdcEngine( var transactionTopic = String.format("%s.%s", topicPrefix, DEFAULT_TRANSACTION_TOPIC); var consumer = new DbzChangeEventConsumer( + connector, sourceId, heartbeatTopicPrefix, transactionTopic, diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngineRunner.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngineRunner.java index b223d0dfba142..a64c11745cf91 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngineRunner.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzCdcEngineRunner.java @@ -14,7 +14,6 @@ package com.risingwave.connector.source.core; -import com.risingwave.connector.api.source.*; import com.risingwave.connector.source.common.DbzConnectorConfig; import com.risingwave.connector.source.common.DbzSourceUtils; import com.risingwave.java.binding.Binding; @@ -43,6 +42,7 @@ public static DbzCdcEngineRunner newCdcEngineRunner( var sourceId = config.getSourceId(); var engine = new DbzCdcEngine( + config.getSourceType(), config.getSourceId(), config.getResolvedDebeziumProps(), (success, message, error) -> { @@ -76,6 +76,7 @@ public static DbzCdcEngineRunner create(DbzConnectorConfig config, long channelP final DbzCdcEngineRunner finalRunner = runner; var engine = new DbzCdcEngine( + config.getSourceType(), config.getSourceId(), config.getResolvedDebeziumProps(), (success, message, error) -> { @@ -144,7 +145,9 @@ public boolean start() throws InterruptedException { .getProperty(CommonConnectorConfig.TOPIC_PREFIX.name()); startOk = DbzSourceUtils.waitForStreamingRunning( - config.getSourceType(), databaseServerName); + config.getSourceType(), + databaseServerName, + config.getWaitStreamingStartTimeout()); } running.set(true); diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzChangeEventConsumer.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzChangeEventConsumer.java index b6d030537c105..98a0a171ec4cc 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzChangeEventConsumer.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/DbzChangeEventConsumer.java @@ -14,6 +14,7 @@ package com.risingwave.connector.source.core; +import com.risingwave.connector.api.source.SourceTypeE; import com.risingwave.connector.cdc.debezium.internal.DebeziumOffset; import com.risingwave.connector.cdc.debezium.internal.DebeziumOffsetSerializer; import com.risingwave.proto.ConnectorServiceProto.CdcMessage; @@ -49,6 +50,8 @@ public class DbzChangeEventConsumer static final Logger LOG = LoggerFactory.getLogger(DbzChangeEventConsumer.class); private final BlockingQueue outputChannel; + + private final SourceTypeE connector; private final long sourceId; private final JsonConverter payloadConverter; private final JsonConverter keyConverter; @@ -59,10 +62,12 @@ public class DbzChangeEventConsumer currentRecordCommitter; DbzChangeEventConsumer( + SourceTypeE connector, long sourceId, String heartbeatTopicPrefix, String transactionTopic, BlockingQueue queue) { + this.connector = connector; this.sourceId = sourceId; this.outputChannel = queue; this.heartbeatTopicPrefix = heartbeatTopicPrefix; @@ -87,6 +92,14 @@ public class DbzChangeEventConsumer this.keyConverter = keyConverter; } + /** + * Postgres and Oracle connectors need to commit the offset to the upstream database, so that we + * need to wait for the epoch commit before committing the record offset. + */ + private boolean noNeedCommitOffset() { + return connector != SourceTypeE.POSTGRES; + } + private EventType getEventType(SourceRecord record) { if (isHeartbeatEvent(record)) { return EventType.HEARTBEAT; @@ -189,11 +202,15 @@ var record = event.value(); byte[] key = keyConverter.fromConnectData( record.topic(), record.keySchema(), record.key()); + String msgPayload = + payload == null ? "" : new String(payload, StandardCharsets.UTF_8); + // key can be null if the table has no primary key + String msgKey = key == null ? "" : new String(key, StandardCharsets.UTF_8); var message = msgBuilder .setFullTableName(fullTableName) - .setPayload(new String(payload, StandardCharsets.UTF_8)) - .setKey(new String(key, StandardCharsets.UTF_8)) + .setPayload(msgPayload) + .setKey(msgKey) .setSourceTsMs(sourceTsMs) .build(); LOG.debug( @@ -207,6 +224,9 @@ var record = event.value(); default: break; } + if (noNeedCommitOffset()) { + committer.markProcessed(event); + } } LOG.debug("recv {} events", respBuilder.getEventsCount()); @@ -216,6 +236,9 @@ var record = event.value(); var response = respBuilder.build(); outputChannel.put(response); } + if (noNeedCommitOffset()) { + committer.markBatchFinished(); + } } public BlockingQueue getOutputChannel() { diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/JniDbzSourceHandler.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/JniDbzSourceHandler.java index 30092195f40f2..dd03d0adaca8a 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/JniDbzSourceHandler.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/source/core/JniDbzSourceHandler.java @@ -34,8 +34,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** handler for starting a debezium source connectors for jni */ - /** handler for starting a debezium source connectors for jni */ public class JniDbzSourceHandler { static final Logger LOG = LoggerFactory.getLogger(JniDbzSourceHandler.class); @@ -56,10 +54,6 @@ public static void runJniDbzSourceThread(byte[] getEventStreamRequestBytes, long throws Exception { var request = ConnectorServiceProto.GetEventStreamRequest.parseFrom(getEventStreamRequestBytes); - - // For jni.rs - java.lang.Thread.currentThread() - .setContextClassLoader(java.lang.ClassLoader.getSystemClassLoader()); // userProps extracted from request, underlying implementation is UnmodifiableMap Map mutableUserProps = new HashMap<>(request.getPropertiesMap()); mutableUserProps.put("source.id", Long.toString(request.getSourceId())); diff --git a/java/connector-node/risingwave-connector-service/src/main/resources/debezium.properties b/java/connector-node/risingwave-connector-service/src/main/resources/debezium.properties index 4d85e1b98c78f..8b887cadbab02 100644 --- a/java/connector-node/risingwave-connector-service/src/main/resources/debezium.properties +++ b/java/connector-node/risingwave-connector-service/src/main/resources/debezium.properties @@ -11,7 +11,7 @@ decimal.handling.mode=${debezium.decimal.handling.mode:-string} interval.handling.mode=string max.batch.size=${debezium.max.batch.size:-1024} max.queue.size=${debezium.max.queue.size:-8192} -time.precision.mode=connect +time.precision.mode=adaptive_time_microseconds # Quoted from the debezium document: # > Your application should always properly stop the engine to ensure graceful and complete # > shutdown and that each source record is sent to the application exactly one time. @@ -19,3 +19,4 @@ time.precision.mode=connect # handle conflicts in the mview operator, thus we don't need to obey the above # instructions. So we decrease the wait time here to reclaim jvm thread faster. debezium.embedded.shutdown.pause.before.interrupt.ms=1 +offset.flush.interval.ms=60000 diff --git a/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties b/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties index bcf99d0c29580..0c62a51986b1c 100644 --- a/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties +++ b/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties @@ -15,6 +15,8 @@ schema.history.internal.store.only.captured.databases.ddl=true # default to disable schema change events include.schema.changes=${debezium.include.schema.changes:-false} database.server.id=${server.id} +# default to use unencrypted connection +database.ssl.mode=${ssl.mode:-disabled} # default heartbeat interval 60 seconds heartbeat.interval.ms=${debezium.heartbeat.interval.ms:-60000} # In sharing cdc mode, we will subscribe to multiple tables in the given database, @@ -23,5 +25,6 @@ name=${hostname}:${port}:${database.name}.${table.name:-RW_CDC_Sharing} # In sharing cdc mode, transaction metadata will be enabled in frontend provide.transaction.metadata=${transactional:-false} ## Pass-through driver properties -# set connector timezone to UTC(+00:00) +# force connection session timezone to UTC(+00:00) driver.connectionTimeZone=+00:00 +driver.forceConnectionTimeZoneToSession=true diff --git a/java/connector-node/risingwave-connector-service/src/main/resources/postgres.properties b/java/connector-node/risingwave-connector-service/src/main/resources/postgres.properties index 326138403d3b2..06c4210fcf468 100644 --- a/java/connector-node/risingwave-connector-service/src/main/resources/postgres.properties +++ b/java/connector-node/risingwave-connector-service/src/main/resources/postgres.properties @@ -17,6 +17,9 @@ publication.autocreate.mode=disabled publication.name=${publication.name:-rw_publication} # default heartbeat interval 5 mins heartbeat.interval.ms=${debezium.heartbeat.interval.ms:-300000} +# emit a WAL message to the replication stream +# see https://github.com/risingwavelabs/risingwave/issues/16697 for more details +heartbeat.action.query=SELECT pg_logical_emit_message(false, 'heartbeat', now()::varchar) # In sharing cdc source mode, we will subscribe to multiple tables in the given database, # so here we set ${table.name} to a default value `RW_CDC_Sharing` just for display. name=${hostname}:${port}:${database.name}.${schema.name}.${table.name:-RW_CDC_Sharing} diff --git a/java/connector-node/risingwave-connector-test/pom.xml b/java/connector-node/risingwave-connector-test/pom.xml index 14b1c7bd65fc0..d3d47b0bc4571 100644 --- a/java/connector-node/risingwave-connector-test/pom.xml +++ b/java/connector-node/risingwave-connector-test/pom.xml @@ -128,13 +128,13 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + 2.13.5 test com.fasterxml.jackson.core jackson-core - ${jackson.version} + 2.13.5 test diff --git a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/DeserializerTest.java b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/DeserializerTest.java deleted file mode 100644 index 9284a2ef8fd20..0000000000000 --- a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/DeserializerTest.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.connector.sink; - -import com.risingwave.connector.JsonDeserializer; -import com.risingwave.connector.TestUtils; -import com.risingwave.connector.api.sink.SinkRow; -import com.risingwave.proto.ConnectorServiceProto; -import com.risingwave.proto.ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch.JsonPayload; -import com.risingwave.proto.Data; -import junit.framework.TestCase; - -public class DeserializerTest extends TestCase { - public void testJsonDeserializer() { - JsonDeserializer deserializer = new JsonDeserializer(TestUtils.getMockTableSchema()); - JsonPayload jsonPayload = - JsonPayload.newBuilder() - .addRowOps( - JsonPayload.RowOp.newBuilder() - .setOpType(Data.Op.INSERT) - .setLine("{\"id\": 1, \"name\": \"John\"}") - .build()) - .build(); - ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch writeBatch = - ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch.newBuilder() - .setJsonPayload(jsonPayload) - .build(); - SinkRow outcome = deserializer.deserialize(writeBatch).iterator().next(); - assertEquals(outcome.get(0), 1); - assertEquals(outcome.get(1), "John"); - } -} diff --git a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java index f0dcc4c1c4930..885fc7eb927a3 100644 --- a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java +++ b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java @@ -14,10 +14,10 @@ package com.risingwave.connector.sink; +import com.google.protobuf.ByteString; import com.risingwave.connector.SinkWriterStreamObserver; import com.risingwave.connector.TestUtils; import com.risingwave.proto.ConnectorServiceProto; -import com.risingwave.proto.Data.Op; import io.grpc.stub.StreamObserver; import java.util.Map; import org.junit.Assert; @@ -94,7 +94,6 @@ public void testOnNext_syncValidation() { .setStart( ConnectorServiceProto.SinkWriterStreamRequest.StartSink.newBuilder() .setSinkParam(fileSinkParam) - .setFormat(ConnectorServiceProto.SinkPayloadFormat.JSON) .build()) .build(); ConnectorServiceProto.SinkWriterStreamRequest firstSync = @@ -138,7 +137,6 @@ public void testOnNext_startEpochValidation() { .setStart( ConnectorServiceProto.SinkWriterStreamRequest.StartSink.newBuilder() .setSinkParam(fileSinkParam) - .setFormat(ConnectorServiceProto.SinkPayloadFormat.JSON) .build()) .build(); ConnectorServiceProto.SinkWriterStreamRequest firstSync = @@ -156,6 +154,8 @@ public void testOnNext_startEpochValidation() { sinkWriterStreamObserver.onNext(firstSync); } + // WARN! This test is skipped in CI pipeline see + // `.github/workflows/connector-node-integration.yml` @Test public void testOnNext_writeValidation() { SinkWriterStreamObserver sinkWriterStreamObserver; @@ -164,10 +164,16 @@ public void testOnNext_writeValidation() { ConnectorServiceProto.SinkWriterStreamRequest.newBuilder() .setStart( ConnectorServiceProto.SinkWriterStreamRequest.StartSink.newBuilder() - .setFormat(ConnectorServiceProto.SinkPayloadFormat.JSON) .setSinkParam(fileSinkParam)) .build(); + // Encoded StreamChunk: 1 'test' + byte[] data1 = + new byte[] { + 8, 1, 18, 1, 1, 26, 20, 8, 2, 18, 6, 8, 1, 18, 2, 1, 1, 26, 8, 8, 1, 18, 4, 0, + 0, 0, 1, 26, 42, 8, 6, 18, 6, 8, 1, 18, 2, 1, 1, 26, 20, 8, 1, 18, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 26, 8, 8, 1, 18, 4, 116, 101, 115, 116 + }; ConnectorServiceProto.SinkWriterStreamRequest firstWrite = ConnectorServiceProto.SinkWriterStreamRequest.newBuilder() .setWriteBatch( @@ -175,19 +181,11 @@ public void testOnNext_writeValidation() { .newBuilder() .setEpoch(0) .setBatchId(1) - .setJsonPayload( + .setStreamChunkPayload( ConnectorServiceProto.SinkWriterStreamRequest - .WriteBatch.JsonPayload.newBuilder() - .addRowOps( - ConnectorServiceProto - .SinkWriterStreamRequest - .WriteBatch.JsonPayload - .RowOp.newBuilder() - .setOpType(Op.INSERT) - .setLine( - "{\"id\": 1, \"name\": \"test\"}") - .build())) - .build()) + .WriteBatch.StreamChunkPayload.newBuilder() + .setBinaryData(ByteString.copyFrom(data1)) + .build())) .build(); ConnectorServiceProto.SinkWriterStreamRequest firstSync = @@ -199,6 +197,13 @@ public void testOnNext_writeValidation() { .build()) .build(); + // Encoded StreamChunk: 2 'test' + byte[] data2 = + new byte[] { + 8, 1, 18, 1, 1, 26, 20, 8, 2, 18, 6, 8, 1, 18, 2, 1, 1, 26, 8, 8, 1, 18, 4, 0, + 0, 0, 2, 26, 42, 8, 6, 18, 6, 8, 1, 18, 2, 1, 1, 26, 20, 8, 1, 18, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 26, 8, 8, 1, 18, 4, 116, 101, 115, 116 + }; ConnectorServiceProto.SinkWriterStreamRequest secondWrite = ConnectorServiceProto.SinkWriterStreamRequest.newBuilder() .setWriteBatch( @@ -206,19 +211,11 @@ public void testOnNext_writeValidation() { .newBuilder() .setEpoch(1) .setBatchId(2) - .setJsonPayload( + .setStreamChunkPayload( ConnectorServiceProto.SinkWriterStreamRequest - .WriteBatch.JsonPayload.newBuilder() - .addRowOps( - ConnectorServiceProto - .SinkWriterStreamRequest - .WriteBatch.JsonPayload - .RowOp.newBuilder() - .setOpType(Op.INSERT) - .setLine( - "{\"id\": 2, \"name\": \"test\"}") - .build())) - .build()) + .WriteBatch.StreamChunkPayload.newBuilder() + .setBinaryData(ByteString.copyFrom(data2)) + .build())) .build(); ConnectorServiceProto.SinkWriterStreamRequest secondWriteWrongEpoch = @@ -228,19 +225,11 @@ public void testOnNext_writeValidation() { .newBuilder() .setEpoch(2) .setBatchId(3) - .setJsonPayload( + .setStreamChunkPayload( ConnectorServiceProto.SinkWriterStreamRequest - .WriteBatch.JsonPayload.newBuilder() - .addRowOps( - ConnectorServiceProto - .SinkWriterStreamRequest - .WriteBatch.JsonPayload - .RowOp.newBuilder() - .setOpType(Op.INSERT) - .setLine( - "{\"id\": 2, \"name\": \"test\"}") - .build())) - .build()) + .WriteBatch.StreamChunkPayload.newBuilder() + .setBinaryData(ByteString.copyFrom(data2)) + .build())) .build(); boolean exceptionThrown = false; @@ -251,7 +240,10 @@ public void testOnNext_writeValidation() { sinkWriterStreamObserver.onNext(firstWrite); } catch (RuntimeException e) { exceptionThrown = true; - Assert.assertTrue(e.getMessage().toLowerCase().contains("batch id")); + if (!e.getMessage().toLowerCase().contains("batch id")) { + e.printStackTrace(); + Assert.fail("Expected `batch id`, but got " + e.getMessage()); + } } if (!exceptionThrown) { Assert.fail("Expected exception not thrown: `invalid batch id`"); @@ -267,7 +259,10 @@ public void testOnNext_writeValidation() { sinkWriterStreamObserver.onNext(secondWriteWrongEpoch); } catch (RuntimeException e) { exceptionThrown = true; - Assert.assertTrue(e.getMessage().toLowerCase().contains("invalid epoch")); + if (!e.getMessage().toLowerCase().contains("invalid epoch")) { + e.printStackTrace(); + Assert.fail("Expected `invalid epoch`, but got " + e.getMessage()); + } } if (!exceptionThrown) { Assert.fail( diff --git a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/elasticsearch/EsSinkTest.java b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/elasticsearch/EsSinkTest.java index 509f71ec1e569..d2873fac9d216 100644 --- a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/elasticsearch/EsSinkTest.java +++ b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/elasticsearch/EsSinkTest.java @@ -19,6 +19,7 @@ import com.google.common.collect.Iterators; import com.google.common.collect.Lists; +import com.risingwave.connector.ElasticRestHighLevelClientAdapter; import com.risingwave.connector.EsSink; import com.risingwave.connector.EsSinkConfig; import com.risingwave.connector.api.TableSchema; @@ -28,10 +29,10 @@ import com.risingwave.proto.Data.Op; import java.io.IOException; import java.util.Map; +import org.apache.http.HttpHost; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; @@ -52,14 +53,14 @@ static TableSchema getTestTableSchema() { public void testEsSink(ElasticsearchContainer container, String username, String password) throws IOException { - EsSink sink = - new EsSink( - new EsSinkConfig(container.getHttpHostAddress()) - .withIndex("test") - .withDelimiter("$") - .withUsername(username) - .withPassword(password), - getTestTableSchema()); + EsSinkConfig config = + new EsSinkConfig(container.getHttpHostAddress()) + .withIndex("test") + .withDelimiter("$") + .withUsername(username) + .withPassword(password); + config.setConnector("elasticsearch"); + EsSink sink = new EsSink(config, getTestTableSchema()); sink.write( Iterators.forArray( new ArraySinkRow( @@ -74,7 +75,9 @@ public void testEsSink(ElasticsearchContainer container, String username, String fail(e.getMessage()); } - RestHighLevelClient client = sink.getClient(); + HttpHost host = HttpHost.create(config.getUrl()); + ElasticRestHighLevelClientAdapter client = + new ElasticRestHighLevelClientAdapter(host, config); SearchRequest searchRequest = new SearchRequest("test"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchAllQuery()); diff --git a/java/connector-node/risingwave-sink-cassandra/src/main/java/com/risingwave/connector/CassandraUtil.java b/java/connector-node/risingwave-sink-cassandra/src/main/java/com/risingwave/connector/CassandraUtil.java index a6be8f7fc89c1..8327893f6da9a 100644 --- a/java/connector-node/risingwave-sink-cassandra/src/main/java/com/risingwave/connector/CassandraUtil.java +++ b/java/connector-node/risingwave-sink-cassandra/src/main/java/com/risingwave/connector/CassandraUtil.java @@ -79,9 +79,10 @@ private static int getCorrespondingCassandraType(DataType dataType) { public static void checkSchema( List columnDescs, Map cassandraColumnDescMap) { - if (columnDescs.size() != cassandraColumnDescMap.size()) { + if (columnDescs.size() > cassandraColumnDescMap.size()) { throw Status.FAILED_PRECONDITION - .withDescription("Don't match in the number of columns in the table") + .withDescription( + "The columns of the sink must be equal to or a superset of the target table's columns.") .asRuntimeException(); } for (ColumnDesc columnDesc : columnDescs) { diff --git a/java/connector-node/risingwave-sink-deltalake/pom.xml b/java/connector-node/risingwave-sink-deltalake/pom.xml index bab3c5320fae2..9a89853ff9f39 100644 --- a/java/connector-node/risingwave-sink-deltalake/pom.xml +++ b/java/connector-node/risingwave-sink-deltalake/pom.xml @@ -18,7 +18,7 @@ 11 11 - 1.12.3 + 1.14.0 true diff --git a/java/connector-node/risingwave-sink-es-7/pom.xml b/java/connector-node/risingwave-sink-es-7/pom.xml index 9c8515098d7d8..4ff4bd76ef109 100644 --- a/java/connector-node/risingwave-sink-es-7/pom.xml +++ b/java/connector-node/risingwave-sink-es-7/pom.xml @@ -51,6 +51,14 @@ org.elasticsearch.client elasticsearch-rest-high-level-client + + org.opensearch + opensearch + + + org.opensearch.client + opensearch-rest-high-level-client + org.apache.httpcomponents httpclient diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkListener.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkListener.java new file mode 100644 index 0000000000000..4ce1165ba1baf --- /dev/null +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkListener.java @@ -0,0 +1,97 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ + +package com.risingwave.connector; + +import com.risingwave.connector.EsSink.RequestTracker; +import org.elasticsearch.action.bulk.BulkRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BulkListener + implements org.elasticsearch.action.bulk.BulkProcessor.Listener, + org.opensearch.action.bulk.BulkProcessor.Listener { + private static final Logger LOG = LoggerFactory.getLogger(EsSink.class); + private final RequestTracker requestTracker; + + public BulkListener(RequestTracker requestTracker) { + this.requestTracker = requestTracker; + } + + @Override + public void beforeBulk(long executionId, org.elasticsearch.action.bulk.BulkRequest request) { + LOG.debug("Sending bulk of {} actions to Elasticsearch.", request.numberOfActions()); + } + + @Override + public void afterBulk( + long executionId, + org.elasticsearch.action.bulk.BulkRequest request, + org.elasticsearch.action.bulk.BulkResponse response) { + if (response.hasFailures()) { + String errMessage = + String.format( + "Bulk of %d actions failed. Failure: %s", + request.numberOfActions(), response.buildFailureMessage()); + this.requestTracker.addErrResult(errMessage); + } else { + this.requestTracker.addOkResult(request.numberOfActions()); + LOG.debug("Sent bulk of {} actions to Elasticsearch.", request.numberOfActions()); + } + } + + /** This method is called when the bulk failed and raised a Throwable */ + @Override + public void afterBulk(long executionId, BulkRequest request, Throwable failure) { + String errMessage = + String.format( + "Bulk of %d actions failed. Failure: %s", + request.numberOfActions(), failure.getMessage()); + this.requestTracker.addErrResult(errMessage); + } + + @Override + public void beforeBulk(long executionId, org.opensearch.action.bulk.BulkRequest request) { + LOG.debug("Sending bulk of {} actions to Opensearch.", request.numberOfActions()); + } + + @Override + public void afterBulk( + long executionId, + org.opensearch.action.bulk.BulkRequest request, + org.opensearch.action.bulk.BulkResponse response) { + if (response.hasFailures()) { + String errMessage = + String.format( + "Bulk of %d actions failed. Failure: %s", + request.numberOfActions(), response.buildFailureMessage()); + this.requestTracker.addErrResult(errMessage); + } else { + this.requestTracker.addOkResult(request.numberOfActions()); + LOG.debug("Sent bulk of {} actions to Opensearch.", request.numberOfActions()); + } + } + + @Override + public void afterBulk( + long executionId, org.opensearch.action.bulk.BulkRequest request, Throwable failure) { + String errMessage = + String.format( + "Bulk of %d actions failed. Failure: %s", + request.numberOfActions(), failure.getMessage()); + this.requestTracker.addErrResult(errMessage); + } +} diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkRequestConsumerFactory.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkProcessorAdapter.java similarity index 58% rename from java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkRequestConsumerFactory.java rename to java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkProcessorAdapter.java index e26248b5fef74..d72ebe2833953 100644 --- a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkRequestConsumerFactory.java +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/BulkProcessorAdapter.java @@ -16,14 +16,14 @@ package com.risingwave.connector; -import java.util.function.BiConsumer; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.bulk.BulkRequest; -import org.elasticsearch.action.bulk.BulkResponse; +import java.util.concurrent.TimeUnit; -/** - * {@link BulkRequestConsumerFactory} is used to bridge incompatible Elasticsearch Java API calls - * across different Elasticsearch versions. - */ -interface BulkRequestConsumerFactory - extends BiConsumer> {} +public interface BulkProcessorAdapter { + public void addRow(String index, String key, String doc); + + public void deleteRow(String index, String key); + + public void flush(); + + public void awaitClose(long timeout, TimeUnit unit) throws InterruptedException; +} diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/ElasticBulkProcessorAdapter.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/ElasticBulkProcessorAdapter.java new file mode 100644 index 0000000000000..de6ab3414f65a --- /dev/null +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/ElasticBulkProcessorAdapter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ + +package com.risingwave.connector; + +import com.risingwave.connector.EsSink.RequestTracker; +import java.util.concurrent.TimeUnit; +import org.elasticsearch.action.bulk.BackoffPolicy; +import org.elasticsearch.action.bulk.BulkProcessor; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.common.unit.ByteSizeUnit; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.xcontent.XContentType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ElasticBulkProcessorAdapter implements BulkProcessorAdapter { + private static final Logger LOG = LoggerFactory.getLogger(EsSink.class); + BulkProcessor esBulkProcessor; + private final RequestTracker requestTracker; + + public ElasticBulkProcessorAdapter( + RequestTracker requestTracker, ElasticRestHighLevelClientAdapter client) { + BulkProcessor.Builder builder = + BulkProcessor.builder( + (bulkRequest, bulkResponseActionListener) -> + client.bulkAsync( + bulkRequest, + RequestOptions.DEFAULT, + bulkResponseActionListener), + new BulkListener(requestTracker)); + // Possible feature: move these to config + // execute the bulk every 10 000 requests + builder.setBulkActions(1000); + // flush the bulk every 5mb + builder.setBulkSize(new ByteSizeValue(5, ByteSizeUnit.MB)); + // flush the bulk every 5 seconds whatever the number of requests + builder.setFlushInterval(TimeValue.timeValueSeconds(5)); + // Set the number of concurrent requests + builder.setConcurrentRequests(1); + // Set a custom backoff policy which will initially wait for 100ms, increase exponentially + // and retries up to three times. + builder.setBackoffPolicy( + BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(100), 3)); + this.esBulkProcessor = builder.build(); + this.requestTracker = requestTracker; + } + + @Override + public void flush() { + esBulkProcessor.flush(); + } + + @Override + public void awaitClose(long timeout, TimeUnit unit) throws InterruptedException { + esBulkProcessor.awaitClose(timeout, unit); + } + + @Override + public void addRow(String index, String key, String doc) { + UpdateRequest updateRequest; + updateRequest = new UpdateRequest(index, "_doc", key).doc(doc, XContentType.JSON); + updateRequest.docAsUpsert(true); + this.requestTracker.addWriteTask(); + this.esBulkProcessor.add(updateRequest); + } + + @Override + public void deleteRow(String index, String key) { + DeleteRequest deleteRequest; + deleteRequest = new DeleteRequest(index, "_doc", key); + this.requestTracker.addWriteTask(); + this.esBulkProcessor.add(deleteRequest); + } +} diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/ElasticRestHighLevelClientAdapter.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/ElasticRestHighLevelClientAdapter.java new file mode 100644 index 0000000000000..c64def3bef8a7 --- /dev/null +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/ElasticRestHighLevelClientAdapter.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ + +package com.risingwave.connector; + +import java.io.IOException; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.client.Cancellable; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.RestHighLevelClientBuilder; + +public class ElasticRestHighLevelClientAdapter implements AutoCloseable { + RestHighLevelClient esClient; + + private static RestClientBuilder configureRestClientBuilder( + RestClientBuilder builder, EsSinkConfig config) { + // Possible config: + // 1. Connection path prefix + // 2. Username and password + if (config.getPassword() != null && config.getUsername() != null) { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + AuthScope.ANY, + new UsernamePasswordCredentials(config.getUsername(), config.getPassword())); + builder.setHttpClientConfigCallback( + httpClientBuilder -> + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); + } + // 3. Timeout + return builder; + } + + public ElasticRestHighLevelClientAdapter(HttpHost host, EsSinkConfig config) { + this.esClient = + new RestHighLevelClientBuilder( + configureRestClientBuilder(RestClient.builder(host), config) + .build()) + .setApiCompatibilityMode(true) + .build(); + } + + @Override + public void close() throws IOException { + esClient.close(); + } + + public boolean ping(RequestOptions options) throws IOException { + boolean flag = esClient.ping(options); + return flag; + } + + public Cancellable bulkAsync( + BulkRequest bulkRequest, + RequestOptions options, + ActionListener listener) { + Cancellable cancellable = esClient.bulkAsync(bulkRequest, options, listener); + return cancellable; + } + + public SearchResponse search(SearchRequest searchRequest, RequestOptions options) + throws IOException { + return this.esClient.search(searchRequest, options); + } +} diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSink.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSink.java index cc5977a9c208c..315fc800a2ef0 100644 --- a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSink.java +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSink.java @@ -1,16 +1,18 @@ -// Copyright 2024 RisingWave Labs -// -// 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. +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ package com.risingwave.connector; @@ -25,25 +27,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.elasticsearch.action.bulk.BackoffPolicy; -import org.elasticsearch.action.bulk.BulkProcessor; -import org.elasticsearch.action.bulk.BulkRequest; -import org.elasticsearch.action.bulk.BulkResponse; -import org.elasticsearch.action.delete.DeleteRequest; -import org.elasticsearch.action.update.UpdateRequest; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.client.RestHighLevelClientBuilder; -import org.elasticsearch.common.unit.ByteSizeUnit; -import org.elasticsearch.common.unit.ByteSizeValue; -import org.elasticsearch.core.TimeValue; -import org.elasticsearch.xcontent.XContentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,8 +48,7 @@ public class EsSink extends SinkWriterBase { private static final String ERROR_REPORT_TEMPLATE = "Error message %s"; private final EsSinkConfig config; - private BulkProcessor bulkProcessor; - private final RestHighLevelClient client; + private BulkProcessorAdapter bulkProcessor; // Used to handle the return message of ES and throw errors private final RequestTracker requestTracker; @@ -167,156 +149,36 @@ public EsSink(EsSinkConfig config, TableSchema tableSchema) { this.requestTracker = new RequestTracker(); // ApiCompatibilityMode is enabled to ensure the client can talk to newer version es sever. - this.client = - new RestHighLevelClientBuilder( - configureRestClientBuilder(RestClient.builder(host), config) - .build()) - .setApiCompatibilityMode(true) - .build(); - // Test connection - try { - boolean isConnected = this.client.ping(RequestOptions.DEFAULT); - if (!isConnected) { - throw Status.INVALID_ARGUMENT - .withDescription("Cannot connect to " + config.getUrl()) - .asRuntimeException(); - } - } catch (Exception e) { - throw Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException(); - } - this.bulkProcessor = createBulkProcessor(this.requestTracker); - } - - private static RestClientBuilder configureRestClientBuilder( - RestClientBuilder builder, EsSinkConfig config) { - // Possible config: - // 1. Connection path prefix - // 2. Username and password - if (config.getPassword() != null && config.getUsername() != null) { - final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials( - AuthScope.ANY, - new UsernamePasswordCredentials(config.getUsername(), config.getPassword())); - builder.setHttpClientConfigCallback( - httpClientBuilder -> - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); - } - // 3. Timeout - return builder; - } - - private BulkProcessor.Builder applyBulkConfig( - RestHighLevelClient client, EsSinkConfig config, BulkProcessor.Listener listener) { - BulkProcessor.Builder builder = - BulkProcessor.builder( - (BulkRequestConsumerFactory) - (bulkRequest, bulkResponseActionListener) -> - client.bulkAsync( - bulkRequest, - RequestOptions.DEFAULT, - bulkResponseActionListener), - listener); - // Possible feature: move these to config - // execute the bulk every 10 000 requests - builder.setBulkActions(1000); - // flush the bulk every 5mb - builder.setBulkSize(new ByteSizeValue(5, ByteSizeUnit.MB)); - // flush the bulk every 5 seconds whatever the number of requests - builder.setFlushInterval(TimeValue.timeValueSeconds(5)); - // Set the number of concurrent requests - builder.setConcurrentRequests(1); - // Set a custom backoff policy which will initially wait for 100ms, increase exponentially - // and retries up to three times. - builder.setBackoffPolicy( - BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(100), 3)); - return builder; - } - - private BulkProcessor createBulkProcessor(RequestTracker requestTracker) { - BulkProcessor.Builder builder = - applyBulkConfig(this.client, this.config, new BulkListener(requestTracker)); - return builder.build(); - } - - private class BulkListener implements BulkProcessor.Listener { - private final RequestTracker requestTracker; - - public BulkListener(RequestTracker requestTracker) { - this.requestTracker = requestTracker; - } - - /** This method is called just before bulk is executed. */ - @Override - public void beforeBulk(long executionId, BulkRequest request) { - LOG.debug("Sending bulk of {} actions to Elasticsearch.", request.numberOfActions()); - } - - /** This method is called after bulk execution. */ - @Override - public void afterBulk(long executionId, BulkRequest request, BulkResponse response) { - if (response.hasFailures()) { - String errMessage = - String.format( - "Bulk of %d actions failed. Failure: %s", - request.numberOfActions(), response.buildFailureMessage()); - this.requestTracker.addErrResult(errMessage); - } else { - this.requestTracker.addOkResult(request.numberOfActions()); - LOG.debug("Sent bulk of {} actions to Elasticsearch.", request.numberOfActions()); - } - } - - /** This method is called when the bulk failed and raised a Throwable */ - @Override - public void afterBulk(long executionId, BulkRequest request, Throwable failure) { - String errMessage = - String.format( - "Bulk of %d actions failed. Failure: %s", - request.numberOfActions(), failure.getMessage()); - this.requestTracker.addErrResult(errMessage); - } - } - - private void processUpsert(SinkRow row) throws JsonMappingException, JsonProcessingException { - final String index = (String) row.get(0); - final String key = (String) row.get(1); - String doc = (String) row.get(2); - - UpdateRequest updateRequest; - if (config.getIndex() != null) { - updateRequest = - new UpdateRequest(config.getIndex(), "_doc", key).doc(doc, XContentType.JSON); + if (config.getConnector().equals("elasticsearch")) { + ElasticRestHighLevelClientAdapter client = + new ElasticRestHighLevelClientAdapter(host, config); + this.bulkProcessor = new ElasticBulkProcessorAdapter(this.requestTracker, client); + } else if (config.getConnector().equals("opensearch")) { + OpensearchRestHighLevelClientAdapter client = + new OpensearchRestHighLevelClientAdapter(host, config); + this.bulkProcessor = new OpensearchBulkProcessorAdapter(this.requestTracker, client); } else { - updateRequest = new UpdateRequest(index, "_doc", key).doc(doc, XContentType.JSON); + throw new RuntimeException("Sink type must be elasticsearch or opensearch"); } - updateRequest.docAsUpsert(true); - this.requestTracker.addWriteTask(); - bulkProcessor.add(updateRequest); } - private void processDelete(SinkRow row) throws JsonMappingException, JsonProcessingException { - final String index = (String) row.get(0); + private void writeRow(SinkRow row) throws JsonMappingException, JsonProcessingException { final String key = (String) row.get(1); - - DeleteRequest deleteRequest; - if (config.getIndex() != null) { - deleteRequest = new DeleteRequest(config.getIndex(), "_doc", key); + String doc = (String) row.get(2); + final String index; + if (config.getIndex() == null) { + index = (String) row.get(0); } else { - deleteRequest = new DeleteRequest(index, "_doc", key); + index = config.getIndex(); } - this.requestTracker.addWriteTask(); - bulkProcessor.add(deleteRequest); - } - - private void writeRow(SinkRow row) throws JsonMappingException, JsonProcessingException { switch (row.getOp()) { case INSERT: case UPDATE_INSERT: - processUpsert(row); + this.bulkProcessor.addRow(index, key, doc); break; case DELETE: case UPDATE_DELETE: - processDelete(row); + this.bulkProcessor.deleteRow(index, key); break; default: throw Status.INVALID_ARGUMENT @@ -353,15 +215,10 @@ public void sync() { public void drop() { try { bulkProcessor.awaitClose(100, TimeUnit.SECONDS); - client.close(); } catch (Exception e) { throw io.grpc.Status.INTERNAL .withDescription(String.format(ERROR_REPORT_TEMPLATE, e.getMessage())) .asRuntimeException(); } } - - public RestHighLevelClient getClient() { - return client; - } } diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSinkFactory.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSinkFactory.java index f3fa3bfa16c3b..03e888a892df3 100644 --- a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSinkFactory.java +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/EsSinkFactory.java @@ -1,16 +1,18 @@ -// Copyright 2024 RisingWave Labs -// -// 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. +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ package com.risingwave.connector; @@ -23,17 +25,8 @@ import com.risingwave.proto.Catalog; import com.risingwave.proto.Data; import io.grpc.Status; -import java.io.IOException; import java.util.Map; import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,34 +83,30 @@ public void validate( } // 2. check connection - RestClientBuilder builder = RestClient.builder(host); - if (config.getPassword() != null && config.getUsername() != null) { - final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials( - AuthScope.ANY, - new UsernamePasswordCredentials(config.getUsername(), config.getPassword())); - builder.setHttpClientConfigCallback( - httpClientBuilder -> - httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); - } - RestHighLevelClient client = new RestHighLevelClient(builder); - // Test connection try { - boolean isConnected = client.ping(RequestOptions.DEFAULT); - if (!isConnected) { - throw Status.INVALID_ARGUMENT - .withDescription("Cannot connect to " + config.getUrl()) - .asRuntimeException(); + if (config.getConnector().equals("elasticsearch")) { + ElasticRestHighLevelClientAdapter esClient = + new ElasticRestHighLevelClientAdapter(host, config); + if (!esClient.ping(org.elasticsearch.client.RequestOptions.DEFAULT)) { + throw Status.INVALID_ARGUMENT + .withDescription("Cannot connect to " + config.getUrl()) + .asRuntimeException(); + } + esClient.close(); + } else if (config.getConnector().equals("opensearch")) { + OpensearchRestHighLevelClientAdapter opensearchClient = + new OpensearchRestHighLevelClientAdapter(host, config); + if (!opensearchClient.ping(org.opensearch.client.RequestOptions.DEFAULT)) { + throw Status.INVALID_ARGUMENT + .withDescription("Cannot connect to " + config.getUrl()) + .asRuntimeException(); + } + opensearchClient.close(); + } else { + throw new RuntimeException("Sink type must be elasticsearch or opensearch"); } } catch (Exception e) { throw Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException(); } - - // 3. close client - try { - client.close(); - } catch (IOException e) { - throw Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException(); - } } } diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/OpensearchBulkProcessorAdapter.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/OpensearchBulkProcessorAdapter.java new file mode 100644 index 0000000000000..d5d8cdc3d237d --- /dev/null +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/OpensearchBulkProcessorAdapter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ + +package com.risingwave.connector; + +import com.risingwave.connector.EsSink.RequestTracker; +import java.util.concurrent.TimeUnit; +import org.opensearch.action.bulk.BackoffPolicy; +import org.opensearch.action.bulk.BulkProcessor; +import org.opensearch.action.delete.DeleteRequest; +import org.opensearch.action.update.UpdateRequest; +import org.opensearch.client.RequestOptions; +import org.opensearch.common.unit.TimeValue; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.unit.ByteSizeUnit; +import org.opensearch.core.common.unit.ByteSizeValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OpensearchBulkProcessorAdapter implements BulkProcessorAdapter { + private static final Logger LOG = LoggerFactory.getLogger(EsSink.class); + private final RequestTracker requestTracker; + BulkProcessor opensearchBulkProcessor; + + public OpensearchBulkProcessorAdapter( + RequestTracker requestTracker, OpensearchRestHighLevelClientAdapter client) { + BulkProcessor.Builder builder = + BulkProcessor.builder( + (bulkRequest, bulkResponseActionListener) -> + client.bulkAsync( + bulkRequest, + RequestOptions.DEFAULT, + bulkResponseActionListener), + new BulkListener(requestTracker)); + // Possible feature: move these to config + // execute the bulk every 10 000 requests + builder.setBulkActions(1000); + // flush the bulk every 5mb + builder.setBulkSize(new ByteSizeValue(5, ByteSizeUnit.MB)); + // flush the bulk every 5 seconds whatever the number of requests + builder.setFlushInterval(TimeValue.timeValueSeconds(5)); + // Set the number of concurrent requests + builder.setConcurrentRequests(1); + // Set a custom backoff policy which will initially wait for 100ms, increase exponentially + // and retries up to three times. + builder.setBackoffPolicy( + BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(100), 3)); + this.opensearchBulkProcessor = builder.build(); + this.requestTracker = requestTracker; + } + + @Override + public void flush() { + opensearchBulkProcessor.flush(); + } + + @Override + public void awaitClose(long timeout, TimeUnit unit) throws InterruptedException { + opensearchBulkProcessor.awaitClose(timeout, unit); + } + + @Override + public void addRow(String index, String key, String doc) { + UpdateRequest updateRequest; + updateRequest = new UpdateRequest(index, key).doc(doc, XContentType.JSON); + updateRequest.docAsUpsert(true); + this.requestTracker.addWriteTask(); + this.opensearchBulkProcessor.add(updateRequest); + } + + @Override + public void deleteRow(String index, String key) { + DeleteRequest deleteRequest; + deleteRequest = new DeleteRequest(index, key); + this.requestTracker.addWriteTask(); + this.opensearchBulkProcessor.add(deleteRequest); + } +} diff --git a/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/OpensearchRestHighLevelClientAdapter.java b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/OpensearchRestHighLevelClientAdapter.java new file mode 100644 index 0000000000000..5f3773b0a91aa --- /dev/null +++ b/java/connector-node/risingwave-sink-es-7/src/main/java/com/risingwave/connector/OpensearchRestHighLevelClientAdapter.java @@ -0,0 +1,78 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ + +package com.risingwave.connector; + +import java.io.IOException; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.opensearch.action.bulk.BulkRequest; +import org.opensearch.action.bulk.BulkResponse; +import org.opensearch.client.Cancellable; +import org.opensearch.client.RequestOptions; +import org.opensearch.client.RestClientBuilder; +import org.opensearch.client.RestHighLevelClient; +import org.opensearch.core.action.ActionListener; + +public class OpensearchRestHighLevelClientAdapter implements AutoCloseable { + RestHighLevelClient opensearchClient; + + private static RestClientBuilder configureRestClientBuilder( + RestClientBuilder builder, EsSinkConfig config) { + // Possible config: + // 1. Connection path prefix + // 2. Username and password + if (config.getPassword() != null && config.getUsername() != null) { + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + AuthScope.ANY, + new UsernamePasswordCredentials(config.getUsername(), config.getPassword())); + builder.setHttpClientConfigCallback( + httpClientBuilder -> + httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)); + } + // 3. Timeout + return builder; + } + + public OpensearchRestHighLevelClientAdapter(HttpHost host, EsSinkConfig config) { + this.opensearchClient = + new org.opensearch.client.RestHighLevelClient( + configureRestClientBuilder( + org.opensearch.client.RestClient.builder(host), config)); + } + + @Override + public void close() throws IOException { + opensearchClient.close(); + } + + public boolean ping(org.opensearch.client.RequestOptions options) throws IOException { + boolean flag = opensearchClient.ping(options); + return flag; + } + + public Cancellable bulkAsync( + BulkRequest bulkRequest, + RequestOptions options, + ActionListener listener) { + Cancellable cancellable = opensearchClient.bulkAsync(bulkRequest, options, listener); + return cancellable; + } +} diff --git a/java/connector-node/risingwave-sink-iceberg/pom.xml b/java/connector-node/risingwave-sink-iceberg/pom.xml index 9f733d830a475..d8ef3d6db384a 100644 --- a/java/connector-node/risingwave-sink-iceberg/pom.xml +++ b/java/connector-node/risingwave-sink-iceberg/pom.xml @@ -16,7 +16,7 @@ risingwave-sink-iceberg - 1.4.1 + 1.5.2 11 11 true @@ -108,13 +108,17 @@ software.amazon.awssdk sts + + software.amazon.awssdk + glue + org.postgresql postgresql - mysql - mysql-connector-java + com.mysql + mysql-connector-j org.xerial diff --git a/java/connector-node/risingwave-sink-iceberg/src/main/java/com/risingwave/connector/catalog/GlueCredentialProvider.java b/java/connector-node/risingwave-sink-iceberg/src/main/java/com/risingwave/connector/catalog/GlueCredentialProvider.java new file mode 100644 index 0000000000000..073d481db113e --- /dev/null +++ b/java/connector-node/risingwave-sink-iceberg/src/main/java/com/risingwave/connector/catalog/GlueCredentialProvider.java @@ -0,0 +1,45 @@ +/* + * Copyright 2024 RisingWave Labs + * + * 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. + */ + +package com.risingwave.connector.catalog; + +import java.util.Map; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.utils.Validate; + +/** This class is used to provide a credential to glue catalog */ +public class GlueCredentialProvider implements AwsCredentialsProvider { + private final AwsCredentials credentials; + + private GlueCredentialProvider(AwsCredentials credentials) { + this.credentials = + (AwsCredentials) + Validate.notNull( + credentials, "Credentials must not be null.", new Object[0]); + } + + public static GlueCredentialProvider create(Map config) { + return new GlueCredentialProvider( + AwsBasicCredentials.create( + config.get("glue.access-key-id"), config.get("glue.secret-access-key"))); + } + + public AwsCredentials resolveCredentials() { + return this.credentials; + } +} diff --git a/java/connector-node/risingwave-sink-iceberg/src/main/java/com/risingwave/connector/catalog/JniCatalogWrapper.java b/java/connector-node/risingwave-sink-iceberg/src/main/java/com/risingwave/connector/catalog/JniCatalogWrapper.java index e8c900b37e88d..583747f3b2f3f 100644 --- a/java/connector-node/risingwave-sink-iceberg/src/main/java/com/risingwave/connector/catalog/JniCatalogWrapper.java +++ b/java/connector-node/risingwave-sink-iceberg/src/main/java/com/risingwave/connector/catalog/JniCatalogWrapper.java @@ -74,15 +74,6 @@ public static JniCatalogWrapper create(String name, String klassName, String[] p checkArgument( props.length % 2 == 0, "props should be key-value pairs, but length is: " + props.length); - - // Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); - System.out.println("Current thread name is: " + Thread.currentThread().getName()); - - // try { - // Thread.currentThread().getContextClassLoader().loadClass(klassName); - // } catch (ClassNotFoundException e) { - // throw new RuntimeException(e); - // } try { HashMap config = new HashMap<>(props.length / 2); for (int i = 0; i < props.length; i += 2) { diff --git a/java/connector-node/risingwave-sink-jdbc/pom.xml b/java/connector-node/risingwave-sink-jdbc/pom.xml index 0390d57a66870..52947595c7413 100644 --- a/java/connector-node/risingwave-sink-jdbc/pom.xml +++ b/java/connector-node/risingwave-sink-jdbc/pom.xml @@ -38,8 +38,8 @@ postgresql - mysql - mysql-connector-java + com.mysql + mysql-connector-j diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java index dfdc01f4a36ed..10aa371c50aec 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java @@ -23,6 +23,7 @@ import io.grpc.Status; import java.sql.*; import java.util.*; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,26 +53,33 @@ public JDBCSink(JDBCSinkConfig config, TableSchema tableSchema) { this.config = config; try { conn = JdbcUtils.getConnection(config.getJdbcUrl()); - // Retrieve primary keys and column type mappings from the database - this.pkColumnNames = - getPkColumnNames(conn, config.getTableName(), config.getSchemaName()); + // Table schema has been validated before, so we get the PK from it directly + this.pkColumnNames = tableSchema.getPrimaryKeys(); // column name -> java.sql.Types Map columnTypeMapping = getColumnTypeMapping(conn, config.getTableName(), config.getSchemaName()); - // create an array that each slot corresponding to each column in TableSchema - var columnSqlTypes = new int[tableSchema.getNumColumns()]; - for (int columnIdx = 0; columnIdx < tableSchema.getNumColumns(); columnIdx++) { - var columnName = tableSchema.getColumnNames()[columnIdx]; - columnSqlTypes[columnIdx] = columnTypeMapping.get(columnName); - } + + // A vector of upstream column types + List columnSqlTypes = + Arrays.stream(tableSchema.getColumnNames()) + .map(columnTypeMapping::get) + .collect(Collectors.toList()); + + List pkIndices = + tableSchema.getPrimaryKeys().stream() + .map(tableSchema::getColumnIndex) + .collect(Collectors.toList()); + LOG.info( - "schema = {}, table = {}: columnSqlTypes = {}", + "schema = {}, table = {}, tableSchema = {}, columnSqlTypes = {}, pkIndices = {}", config.getSchemaName(), config.getTableName(), - Arrays.toString(columnSqlTypes)); + tableSchema, + columnSqlTypes, + pkIndices); if (factory.isPresent()) { - this.jdbcDialect = factory.get().create(columnSqlTypes); + this.jdbcDialect = factory.get().create(columnSqlTypes, pkIndices); } else { throw Status.INVALID_ARGUMENT .withDescription("Unsupported jdbc url: " + jdbcUrl) @@ -81,6 +89,8 @@ public JDBCSink(JDBCSinkConfig config, TableSchema tableSchema) { "JDBC connection: autoCommit = {}, trxn = {}", conn.getAutoCommit(), conn.getTransactionIsolation()); + // Commit the `getTransactionIsolation` + conn.commit(); jdbcStatements = new JdbcStatements(conn); } catch (SQLException e) { @@ -117,28 +127,6 @@ private static Map getColumnTypeMapping( return columnTypeMap; } - private static List getPkColumnNames( - Connection conn, String tableName, String schemaName) { - List pkColumnNames = new ArrayList<>(); - try { - var pks = conn.getMetaData().getPrimaryKeys(null, schemaName, tableName); - while (pks.next()) { - pkColumnNames.add(pks.getString(JDBC_COLUMN_NAME_KEY)); - } - } catch (SQLException e) { - throw Status.INTERNAL - .withDescription( - String.format(ERROR_REPORT_TEMPLATE, e.getSQLState(), e.getMessage())) - .asRuntimeException(); - } - LOG.info( - "schema = {}, table = {}: detected pk column = {}", - schemaName, - tableName, - pkColumnNames); - return pkColumnNames; - } - @Override public boolean write(Iterable rows) { final int maxRetryCount = 4; @@ -266,10 +254,7 @@ public void prepareUpsert(SinkRow row) { break; case UPDATE_INSERT: if (!updateFlag) { - throw Status.FAILED_PRECONDITION - .withDescription( - "an UPDATE_DELETE should precede an UPDATE_INSERT") - .asRuntimeException(); + LOG.warn("Missing an UPDATE_DELETE precede an UPDATE_INSERT"); } jdbcDialect.bindUpsertStatement(upsertStatement, conn, tableSchema, row); updateFlag = false; @@ -303,11 +288,7 @@ public void prepareDelete(SinkRow row) { .asRuntimeException(); } try { - int placeholderIdx = 1; - for (String primaryKey : pkColumnNames) { - Object fromRow = tableSchema.getFromRow(primaryKey, row); - deleteStatement.setObject(placeholderIdx++, fromRow); - } + jdbcDialect.bindDeleteStatement(deleteStatement, tableSchema, row); deleteStatement.addBatch(); } catch (SQLException e) { throw Status.INTERNAL @@ -380,10 +361,7 @@ public void beginEpoch(long epoch) {} @Override public Optional barrier(boolean isCheckpoint) { if (updateFlag) { - throw Status.FAILED_PRECONDITION - .withDescription( - "expected UPDATE_INSERT to complete an UPDATE operation, got `sync`") - .asRuntimeException(); + LOG.warn("expect an UPDATE_INSERT to complete an UPDATE operation, got `sync`"); } return Optional.empty(); } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSinkFactory.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSinkFactory.java index 835c7b9693668..875bda89681de 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSinkFactory.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSinkFactory.java @@ -75,7 +75,11 @@ public void validate( } catch (SQLException e) { LOG.error("failed to connect to target database. jdbcUrl: {}", jdbcUrl, e); throw Status.INVALID_ARGUMENT - .withDescription("failed to connect to target database: " + e.getSQLState()) + .withDescription( + "failed to connect to target database: " + + e.getSQLState() + + ": " + + e.getMessage()) .asRuntimeException(); } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JdbcUtils.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JdbcUtils.java index 7d28e4553fe3e..2360ac96585dd 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JdbcUtils.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JdbcUtils.java @@ -25,6 +25,9 @@ public abstract class JdbcUtils { + static final int CONNECTION_TIMEOUT = 30; + static final int SOCKET_TIMEOUT = 300; + public static Optional getDialectFactory(String jdbcUrl) { if (jdbcUrl.startsWith("jdbc:mysql")) { return Optional.of(new MySqlDialectFactory()); @@ -43,6 +46,16 @@ public static Connection getConnection(String jdbcUrl) throws SQLException { // https://jdbc.postgresql.org/documentation/use/ // https://dev.mysql.com/doc/connectors/en/connector-j-connp-props-networking.html#cj-conn-prop_tcpKeepAlive props.setProperty("tcpKeepAlive", "true"); + + // default timeout in seconds + boolean isPg = jdbcUrl.startsWith("jdbc:postgresql"); + + // postgres use seconds and mysql use milliseconds + int connectTimeout = isPg ? CONNECTION_TIMEOUT : CONNECTION_TIMEOUT * 1000; + int socketTimeout = isPg ? SOCKET_TIMEOUT : SOCKET_TIMEOUT * 1000; + props.setProperty("connectTimeout", String.valueOf(connectTimeout)); + props.setProperty("socketTimeout", String.valueOf(socketTimeout)); + var conn = DriverManager.getConnection(jdbcUrl, props); // disable auto commit can improve performance conn.setAutoCommit(false); diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java index 1be3e949d474d..308f9927457a2 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java @@ -110,4 +110,8 @@ void bindUpsertStatement( void bindInsertIntoStatement( PreparedStatement stmt, Connection conn, TableSchema tableSchema, SinkRow row) throws SQLException; + + /** Bind the values of primary key fields to the {@code DELETE} statement. */ + void bindDeleteStatement(PreparedStatement stmt, TableSchema tableSchema, SinkRow row) + throws SQLException; } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialectFactory.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialectFactory.java index 1bfbf5f9f24c2..75e349489b461 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialectFactory.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialectFactory.java @@ -14,7 +14,9 @@ package com.risingwave.connector.jdbc; +import java.util.List; + public interface JdbcDialectFactory { - JdbcDialect create(int[] columnSqlTypes); + JdbcDialect create(List columnSqlTypes, List pkIndices); } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java index d2cdbd6f1e1d0..2c4ea73448a60 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java @@ -26,6 +26,21 @@ public class MySqlDialect implements JdbcDialect { + private final int[] pkIndices; + private final int[] pkColumnSqlTypes; + + public MySqlDialect(List columnSqlTypes, List pkIndices) { + var columnSqlTypesArr = columnSqlTypes.stream().mapToInt(i -> i).toArray(); + this.pkIndices = pkIndices.stream().mapToInt(i -> i).toArray(); + + // derive sql types for pk columns + var pkColumnSqlTypes = new int[pkIndices.size()]; + for (int i = 0; i < pkIndices.size(); i++) { + pkColumnSqlTypes[i] = columnSqlTypesArr[this.pkIndices[i]]; + } + this.pkColumnSqlTypes = pkColumnSqlTypes; + } + @Override public SchemaTableName createSchemaTableName(String schemaName, String tableName) { return new SchemaTableName(schemaName, tableName); @@ -100,4 +115,15 @@ public void bindInsertIntoStatement( } } } + + @Override + public void bindDeleteStatement(PreparedStatement stmt, TableSchema tableSchema, SinkRow row) + throws SQLException { + // set the values of primary key fields + int placeholderIdx = 1; + for (int i = 0; i < pkIndices.length; ++i) { + Object pkField = row.get(pkIndices[i]); + stmt.setObject(placeholderIdx++, pkField, pkColumnSqlTypes[i]); + } + } } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialectFactory.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialectFactory.java index b6a8948bfe84e..ebc46d86967f3 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialectFactory.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialectFactory.java @@ -14,10 +14,12 @@ package com.risingwave.connector.jdbc; +import java.util.List; + public class MySqlDialectFactory implements JdbcDialectFactory { @Override - public JdbcDialect create(int[] columnSqlTypes) { - return new MySqlDialect(); + public JdbcDialect create(List columnSqlTypes, List pkIndices) { + return new MySqlDialect(columnSqlTypes, pkIndices); } } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java index b7b13be2d154a..6264d9c5eac18 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java @@ -30,9 +30,11 @@ public class PostgresDialect implements JdbcDialect { private final int[] columnSqlTypes; + private final int[] pkIndices; - public PostgresDialect(int[] columnSqlTypes) { - this.columnSqlTypes = columnSqlTypes; + public PostgresDialect(List columnSqlTypes, List pkIndices) { + this.columnSqlTypes = columnSqlTypes.stream().mapToInt(i -> i).toArray(); + this.pkIndices = pkIndices.stream().mapToInt(i -> i).toArray(); } private static final HashMap RW_TYPE_TO_JDBC_TYPE_NAME; @@ -154,4 +156,15 @@ public void bindInsertIntoStatement( } } } + + @Override + public void bindDeleteStatement(PreparedStatement stmt, TableSchema tableSchema, SinkRow row) + throws SQLException { + // set the values of primary key fields + int placeholderIdx = 1; + for (int pkIdx : pkIndices) { + Object pkField = row.get(pkIdx); + stmt.setObject(placeholderIdx++, pkField, columnSqlTypes[pkIdx]); + } + } } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialectFactory.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialectFactory.java index 651875e27dd10..3b6ad2b2bcf5b 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialectFactory.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialectFactory.java @@ -14,10 +14,12 @@ package com.risingwave.connector.jdbc; +import java.util.List; + public class PostgresDialectFactory implements JdbcDialectFactory { @Override - public JdbcDialect create(int[] columnSqlTypes) { - return new PostgresDialect(columnSqlTypes); + public JdbcDialect create(List columnSqlTypes, List pkIndices) { + return new PostgresDialect(columnSqlTypes, pkIndices); } } diff --git a/java/connector-node/risingwave-source-cdc/pom.xml b/java/connector-node/risingwave-source-cdc/pom.xml index fd1dccd3f092f..5ee531ef805e9 100644 --- a/java/connector-node/risingwave-source-cdc/pom.xml +++ b/java/connector-node/risingwave-source-cdc/pom.xml @@ -50,8 +50,8 @@ - mysql - mysql-connector-java + com.mysql + mysql-connector-j com.zendesk diff --git a/java/connector-node/risingwave-source-test/src/test/java/com/risingwave/connector/source/PostgresSourceTest.java b/java/connector-node/risingwave-source-test/src/test/java/com/risingwave/connector/source/PostgresSourceTest.java index 477ae42899f16..5ccb24cef3ffc 100644 --- a/java/connector-node/risingwave-source-test/src/test/java/com/risingwave/connector/source/PostgresSourceTest.java +++ b/java/connector-node/risingwave-source-test/src/test/java/com/risingwave/connector/source/PostgresSourceTest.java @@ -145,7 +145,8 @@ public void testLines() throws Exception { int count = countResult.get(); LOG.info("number of cdc messages received: {}", count); try { - assertEquals(10000, count); + // 10000 rows plus one heartbeat message + assertTrue(count >= 10000); } catch (Exception e) { Assert.fail("validate rpc fail: " + e.getMessage()); } finally { diff --git a/java/dev.md b/java/dev.md index ac20c30fe69fa..148fde173baad 100644 --- a/java/dev.md +++ b/java/dev.md @@ -56,9 +56,3 @@ Config with the following. It may work. "java.format.settings.profile": "Android" } ``` - -## Deploy UDF Library to Maven - -```sh -mvn clean deploy --pl udf --am -``` \ No newline at end of file diff --git a/java/java-binding/src/main/java/com/risingwave/java/binding/Binding.java b/java/java-binding/src/main/java/com/risingwave/java/binding/Binding.java index a16acda73e7fd..db832566fdfa7 100644 --- a/java/java-binding/src/main/java/com/risingwave/java/binding/Binding.java +++ b/java/java-binding/src/main/java/com/risingwave/java/binding/Binding.java @@ -26,6 +26,8 @@ public class Binding { } } + static void ensureInitialized() {} + public static native void tracingSlf4jEvent( String threadName, String name, int level, String message); @@ -33,10 +35,6 @@ public static native void tracingSlf4jEvent( public static native int vnodeCount(); - // hummock iterator method - // Return a pointer to the iterator - static native long iteratorNewHummock(byte[] readPlan); - static native long iteratorNewStreamChunk(long pointer); static native boolean iteratorNext(long pointer); diff --git a/java/java-binding/src/main/java/com/risingwave/java/binding/HummockIterator.java b/java/java-binding/src/main/java/com/risingwave/java/binding/HummockIterator.java index 03282a2dce528..a30391edbd380 100644 --- a/java/java-binding/src/main/java/com/risingwave/java/binding/HummockIterator.java +++ b/java/java-binding/src/main/java/com/risingwave/java/binding/HummockIterator.java @@ -20,8 +20,16 @@ public class HummockIterator implements AutoCloseable { private final long pointer; private boolean isClosed; + static { + Binding.ensureInitialized(); + } + + // hummock iterator method + // Return a pointer to the iterator + private static native long iteratorNewHummock(byte[] readPlan); + public HummockIterator(ReadPlan readPlan) { - this.pointer = Binding.iteratorNewHummock(readPlan.toByteArray()); + this.pointer = iteratorNewHummock(readPlan.toByteArray()); this.isClosed = false; } diff --git a/java/pom.xml b/java/pom.xml index 89df5b870077b..644588c9d6b44 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -37,8 +37,6 @@ proto - udf - udf-example java-binding common-utils java-binding-integration-test @@ -66,7 +64,7 @@ UTF-8 UTF-8 3.21.1 - 1.53.0 + 1.64.0 2.10 0.1.0-SNAPSHOT 2.43.0 @@ -77,19 +75,22 @@ 1.10.0 3.12.0 2.4.2.Final - 2.13.5 + 2.15.0 3.3.1 - 3.3.3 + 3.4.0 7.17.19 + 2.11.1 4.15.0 1.18.0 1.17.6 42.5.5 - 8.0.28 + 8.3.0 4.11.1 3.45.0.0 2.21.42 3.1.3 + 12.0.10 + 1.1.10.5 @@ -178,8 +179,8 @@ ${postgresql.version} - mysql - mysql-connector-java + com.mysql + mysql-connector-j ${mysql.connector.java.version} @@ -197,6 +198,22 @@ elasticsearch-rest-high-level-client ${elasticsearch.version} + + org.opensearch + opensearch + ${opensearch.version} + + + org.opensearch.client + opensearch-rest-high-level-client + ${opensearch.version} + + + org.apache.httpcomponents + httpcore-nio + + + io.grpc grpc-netty-shaded @@ -361,9 +378,9 @@ ${aws.version} - org.apache.hadoop - hadoop-common - ${hadoop.version} + software.amazon.awssdk + glue + ${aws.version} org.apache.hive @@ -385,6 +402,26 @@ hadoop-mapreduce-client-jobclient ${hadoop.version} + + org.eclipse.jetty + jetty-client + ${jetty.version} + + + org.eclipse.jetty + jetty-util + ${jetty.version} + + + org.eclipse.jetty + jetty-http + ${jetty.version} + + + org.xerial.snappy + snappy-java + ${snappy.version} + org.apache.spark spark-sql_2.12 diff --git a/java/udf/CHANGELOG.md b/java/udf/CHANGELOG.md deleted file mode 100644 index fb1f055783225..0000000000000 --- a/java/udf/CHANGELOG.md +++ /dev/null @@ -1,39 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.3] - 2023-12-06 - -### Fixed - -- Fix decimal type output. - -## [0.1.2] - 2023-12-04 - -### Fixed - -- Fix index-out-of-bound error when string or string list is large. -- Fix memory leak. - -## [0.1.1] - 2023-12-03 - -### Added - -- Support struct in struct and struct[] in struct. - -### Changed - -- Bump Arrow version to 14. - -### Fixed - -- Fix unconstrained decimal type. - -## [0.1.0] - 2023-09-01 - -- Initial release. \ No newline at end of file diff --git a/java/udf/README.md b/java/udf/README.md deleted file mode 100644 index 200b897b8b890..0000000000000 --- a/java/udf/README.md +++ /dev/null @@ -1,274 +0,0 @@ -# RisingWave Java UDF SDK - -This library provides a Java SDK for creating user-defined functions (UDF) in RisingWave. - -## Introduction - -RisingWave supports user-defined functions implemented as external functions. -With the RisingWave Java UDF SDK, users can define custom UDFs using Java and start a Java process as a UDF server. -RisingWave can then remotely access the UDF server to execute the defined functions. - -## Installation - -To install the RisingWave Java UDF SDK: - -```sh -git clone https://github.com/risingwavelabs/risingwave.git -cd risingwave/java/udf -mvn install -``` - -Or you can add the following dependency to your `pom.xml` file: - -```xml - - - com.risingwave - risingwave-udf - 0.1.0 - - -``` - - -## Creating a New Project - -> NOTE: You can also start from the [udf-example](../udf-example) project without creating the project from scratch. - -To create a new project using the RisingWave Java UDF SDK, follow these steps: - -```sh -mvn archetype:generate -DgroupId=com.example -DartifactId=udf-example -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false -``` - -Configure your `pom.xml` file as follows: - -```xml - - - 4.0.0 - com.example - udf-example - 1.0-SNAPSHOT - - - - com.risingwave - risingwave-udf - 0.1.0 - - - -``` - -The `--add-opens` flag must be added when running unit tests through Maven: - -```xml - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0 - - --add-opens=java.base/java.nio=ALL-UNNAMED - - - - -``` - -## Scalar Functions - -A user-defined scalar function maps zero, one, or multiple scalar values to a new scalar value. - -In order to define a scalar function, one has to create a new class that implements the `ScalarFunction` -interface in `com.risingwave.functions` and implement exactly one evaluation method named `eval(...)`. -This method must be declared public and non-static. - -Any [data type](#data-types) listed in the data types section can be used as a parameter or return type of an evaluation method. - -Here's an example of a scalar function that calculates the greatest common divisor (GCD) of two integers: - -```java -import com.risingwave.functions.ScalarFunction; - -public class Gcd implements ScalarFunction { - public int eval(int a, int b) { - while (b != 0) { - int temp = b; - b = a % b; - a = temp; - } - return a; - } -} -``` - -> **NOTE:** Differences with Flink -> 1. The `ScalarFunction` is an interface instead of an abstract class. -> 2. Multiple overloaded `eval` methods are not supported. -> 3. Variable arguments such as `eval(Integer...)` are not supported. - -## Table Functions - -A user-defined table function maps zero, one, or multiple scalar values to one or multiple -rows (structured types). - -In order to define a table function, one has to create a new class that implements the `TableFunction` -interface in `com.risingwave.functions` and implement exactly one evaluation method named `eval(...)`. -This method must be declared public and non-static. - -The return type must be an `Iterator` of any [data type](#data-types) listed in the data types section. -Similar to scalar functions, input and output data types are automatically extracted using reflection. -This includes the generic argument T of the return value for determining an output data type. - -Here's an example of a table function that generates a series of integers: - -```java -import com.risingwave.functions.TableFunction; - -public class Series implements TableFunction { - public Iterator eval(int n) { - return java.util.stream.IntStream.range(0, n).iterator(); - } -} -``` - -> **NOTE:** Differences with Flink -> 1. The `TableFunction` is an interface instead of an abstract class. It has no generic arguments. -> 2. Instead of calling `collect` to emit a row, the `eval` method returns an `Iterator` of the output rows. -> 3. Multiple overloaded `eval` methods are not supported. -> 4. Variable arguments such as `eval(Integer...)` are not supported. -> 5. In SQL, table functions can be used in the `FROM` clause directly. `JOIN LATERAL TABLE` is not supported. - -## UDF Server - -To create a UDF server and register functions: - -```java -import com.risingwave.functions.UdfServer; - -public class App { - public static void main(String[] args) { - try (var server = new UdfServer("0.0.0.0", 8815)) { - // register functions - server.addFunction("gcd", new Gcd()); - server.addFunction("series", new Series()); - // start the server - server.start(); - server.awaitTermination(); - } catch (Exception e) { - e.printStackTrace(); - } - } -} -``` - -To run the UDF server, execute the following command: - -```sh -_JAVA_OPTIONS="--add-opens=java.base/java.nio=ALL-UNNAMED" mvn exec:java -Dexec.mainClass="com.example.App" -``` - -## Creating Functions in RisingWave - -```sql -create function gcd(int, int) returns int -as gcd using link 'http://localhost:8815'; - -create function series(int) returns table (x int) -as series using link 'http://localhost:8815'; -``` - -For more detailed information and examples, please refer to the official RisingWave [documentation](https://www.risingwave.dev/docs/current/user-defined-functions/#4-declare-your-functions-in-risingwave). - -## Using Functions in RisingWave - -Once the user-defined functions are created in RisingWave, you can use them in SQL queries just like any built-in functions. Here are a few examples: - -```sql -select gcd(25, 15); - -select * from series(10); -``` - -## Data Types - -The RisingWave Java UDF SDK supports the following data types: - -| SQL Type | Java Type | Notes | -| ---------------- | --------------------------------------- | ------------------ | -| BOOLEAN | boolean, Boolean | | -| SMALLINT | short, Short | | -| INT | int, Integer | | -| BIGINT | long, Long | | -| REAL | float, Float | | -| DOUBLE PRECISION | double, Double | | -| DECIMAL | BigDecimal | | -| DATE | java.time.LocalDate | | -| TIME | java.time.LocalTime | | -| TIMESTAMP | java.time.LocalDateTime | | -| INTERVAL | com.risingwave.functions.PeriodDuration | | -| VARCHAR | String | | -| BYTEA | byte[] | | -| JSONB | String | Use `@DataTypeHint("JSONB") String` as the type. See [example](#jsonb). | -| T[] | T'[] | `T` can be any of the above SQL types. `T'` should be the corresponding Java type.| -| STRUCT<> | user-defined class | Define a data class as the type. See [example](#struct-type). | -| ...others | | Not supported yet. | - -### JSONB - -```java -import com.google.gson.Gson; - -// Returns the i-th element of a JSON array. -public class JsonbAccess implements ScalarFunction { - static Gson gson = new Gson(); - - public @DataTypeHint("JSONB") String eval(@DataTypeHint("JSONB") String json, int index) { - if (json == null) - return null; - var array = gson.fromJson(json, Object[].class); - if (index >= array.length || index < 0) - return null; - var obj = array[index]; - return gson.toJson(obj); - } -} -``` - -```sql -create function jsonb_access(jsonb, int) returns jsonb -as jsonb_access using link 'http://localhost:8815'; -``` - -### Struct Type - -```java -// Split a socket address into host and port. -public static class IpPort implements ScalarFunction { - public static class SocketAddr { - public String host; - public short port; - } - - public SocketAddr eval(String addr) { - var socketAddr = new SocketAddr(); - var parts = addr.split(":"); - socketAddr.host = parts[0]; - socketAddr.port = Short.parseShort(parts[1]); - return socketAddr; - } -} -``` - -```sql -create function ip_port(varchar) returns struct -as ip_port using link 'http://localhost:8815'; -``` - -## Full Example - -You can checkout [udf-example](../udf-example) and use it as a template to create your own UDFs. diff --git a/java/udf/pom.xml b/java/udf/pom.xml deleted file mode 100644 index f747603ca8429..0000000000000 --- a/java/udf/pom.xml +++ /dev/null @@ -1,58 +0,0 @@ - - 4.0.0 - - com.risingwave - risingwave-udf - jar - 0.1.3-SNAPSHOT - - - risingwave-java-root - com.risingwave - 0.1.0-SNAPSHOT - ../pom.xml - - - RisingWave Java UDF SDK - https://docs.risingwave.com/docs/current/udf-java - - - - org.junit.jupiter - junit-jupiter-engine - 5.9.1 - test - - - org.apache.arrow - arrow-vector - 14.0.0 - - - org.apache.arrow - flight-core - 14.0.0 - - - org.slf4j - slf4j-api - 2.0.7 - - - org.slf4j - slf4j-simple - 2.0.7 - - - - - - kr.motd.maven - os-maven-plugin - 1.7.0 - - - - \ No newline at end of file diff --git a/java/udf/src/main/java/com/risingwave/functions/ScalarFunction.java b/java/udf/src/main/java/com/risingwave/functions/ScalarFunction.java deleted file mode 100644 index 5f3fcaf287330..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/ScalarFunction.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -/** - * Base interface for a user-defined scalar function. A user-defined scalar function maps zero, one, - * or multiple scalar values to a new scalar value. - * - *

The behavior of a {@link ScalarFunction} can be defined by implementing a custom evaluation - * method. An evaluation method must be declared publicly, not static, and named eval. - * Multiple overloaded methods named eval are not supported yet. - * - *

By default, input and output data types are automatically extracted using reflection. - * - *

The following examples show how to specify a scalar function: - * - *

{@code
- * // a function that accepts two INT arguments and computes a sum
- * class SumFunction implements ScalarFunction {
- *     public Integer eval(Integer a, Integer b) {
- *         return a + b;
- *     }
- * }
- *
- * // a function that returns a struct type
- * class StructFunction implements ScalarFunction {
- *     public static class KeyValue {
- *         public String key;
- *         public int value;
- *     }
- *
- *     public KeyValue eval(int a) {
- *         KeyValue kv = new KeyValue();
- *         kv.key = a.toString();
- *         kv.value = a;
- *         return kv;
- *     }
- * }
- * }
- */ -public interface ScalarFunction extends UserDefinedFunction {} diff --git a/java/udf/src/main/java/com/risingwave/functions/ScalarFunctionBatch.java b/java/udf/src/main/java/com/risingwave/functions/ScalarFunctionBatch.java deleted file mode 100644 index 5d837d3b370f9..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/ScalarFunctionBatch.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import java.lang.invoke.MethodHandle; -import java.util.Collections; -import java.util.Iterator; -import java.util.function.Function; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; - -/** Batch-processing wrapper over a user-defined scalar function. */ -class ScalarFunctionBatch extends UserDefinedFunctionBatch { - ScalarFunction function; - MethodHandle methodHandle; - Function[] processInputs; - - ScalarFunctionBatch(ScalarFunction function) { - this.function = function; - var method = Reflection.getEvalMethod(function); - this.methodHandle = Reflection.getMethodHandle(method); - this.inputSchema = TypeUtils.methodToInputSchema(method); - this.outputSchema = TypeUtils.methodToOutputSchema(method); - this.processInputs = TypeUtils.methodToProcessInputs(method); - } - - @Override - Iterator evalBatch(VectorSchemaRoot batch, BufferAllocator allocator) { - var row = new Object[batch.getSchema().getFields().size() + 1]; - row[0] = this.function; - var outputValues = new Object[batch.getRowCount()]; - for (int i = 0; i < batch.getRowCount(); i++) { - for (int j = 0; j < row.length - 1; j++) { - var val = batch.getVector(j).getObject(i); - row[j + 1] = this.processInputs[j].apply(val); - } - try { - outputValues[i] = this.methodHandle.invokeWithArguments(row); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - var outputVector = - TypeUtils.createVector( - this.outputSchema.getFields().get(0), allocator, outputValues); - var outputBatch = VectorSchemaRoot.of(outputVector); - return Collections.singleton(outputBatch).iterator(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/TableFunction.java b/java/udf/src/main/java/com/risingwave/functions/TableFunction.java deleted file mode 100644 index ec5b9d214553f..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/TableFunction.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -/** - * Base interface for a user-defined table function. A user-defined table function maps zero, one, - * or multiple scalar values to zero, one, or multiple rows (or structured types). If an output - * record consists of only one field, the structured record can be omitted, and a scalar value can - * be emitted that will be implicitly wrapped into a row by the runtime. - * - *

The behavior of a {@link TableFunction} can be defined by implementing a custom evaluation - * method. An evaluation method must be declared publicly, not static, and named eval. - * The return type must be an Iterator. Multiple overloaded methods named eval are not - * supported yet. - * - *

By default, input and output data types are automatically extracted using reflection. - * - *

The following examples show how to specify a table function: - * - *

{@code
- * // a function that accepts an INT arguments and emits the range from 0 to the
- * // given number.
- * class Series implements TableFunction {
- *     public Iterator eval(int n) {
- *         return java.util.stream.IntStream.range(0, n).iterator();
- *     }
- * }
- *
- * // a function that accepts an String arguments and emits the words of the
- * // given string.
- * class Split implements TableFunction {
- *     public static class Row {
- *         public String word;
- *         public int length;
- *     }
- *
- *     public Iterator eval(String str) {
- *         return Stream.of(str.split(" ")).map(s -> {
- *             Row row = new Row();
- *             row.word = s;
- *             row.length = s.length();
- *             return row;
- *         }).iterator();
- *     }
- * }
- * }
- */ -public interface TableFunction extends UserDefinedFunction {} diff --git a/java/udf/src/main/java/com/risingwave/functions/TableFunctionBatch.java b/java/udf/src/main/java/com/risingwave/functions/TableFunctionBatch.java deleted file mode 100644 index a0e0608e60210..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/TableFunctionBatch.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import java.lang.invoke.MethodHandle; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.function.Function; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; - -/** Batch-processing wrapper over a user-defined table function. */ -class TableFunctionBatch extends UserDefinedFunctionBatch { - TableFunction function; - MethodHandle methodHandle; - Function[] processInputs; - int chunkSize = 1024; - - TableFunctionBatch(TableFunction function) { - this.function = function; - var method = Reflection.getEvalMethod(function); - this.methodHandle = Reflection.getMethodHandle(method); - this.inputSchema = TypeUtils.methodToInputSchema(method); - this.outputSchema = TypeUtils.tableFunctionToOutputSchema(method); - this.processInputs = TypeUtils.methodToProcessInputs(method); - } - - @Override - Iterator evalBatch(VectorSchemaRoot batch, BufferAllocator allocator) { - var outputs = new ArrayList(); - var row = new Object[batch.getSchema().getFields().size() + 1]; - row[0] = this.function; - var indexes = new ArrayList(); - var values = new ArrayList(); - Runnable buildChunk = - () -> { - var fields = this.outputSchema.getFields(); - var indexVector = - TypeUtils.createVector(fields.get(0), allocator, indexes.toArray()); - var valueVector = - TypeUtils.createVector(fields.get(1), allocator, values.toArray()); - indexes.clear(); - values.clear(); - var outputBatch = VectorSchemaRoot.of(indexVector, valueVector); - outputs.add(outputBatch); - }; - for (int i = 0; i < batch.getRowCount(); i++) { - // prepare input row - for (int j = 0; j < row.length - 1; j++) { - var val = batch.getVector(j).getObject(i); - row[j + 1] = this.processInputs[j].apply(val); - } - // call function - Iterator iterator; - try { - iterator = (Iterator) this.methodHandle.invokeWithArguments(row); - } catch (Throwable e) { - throw new RuntimeException(e); - } - // push values - while (iterator.hasNext()) { - indexes.add(i); - values.add(iterator.next()); - // check if we need to flush - if (indexes.size() >= this.chunkSize) { - buildChunk.run(); - } - } - } - if (indexes.size() > 0) { - buildChunk.run(); - } - return outputs.iterator(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/TypeUtils.java b/java/udf/src/main/java/com/risingwave/functions/TypeUtils.java deleted file mode 100644 index 06c2f79858c40..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/TypeUtils.java +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.*; -import org.apache.arrow.vector.complex.ListVector; -import org.apache.arrow.vector.complex.StructVector; -import org.apache.arrow.vector.types.*; -import org.apache.arrow.vector.types.pojo.*; - -class TypeUtils { - /** Convert a string to an Arrow type. */ - static Field stringToField(String typeStr, String name) { - typeStr = typeStr.toUpperCase(); - if (typeStr.equals("BOOLEAN") || typeStr.equals("BOOL")) { - return Field.nullable(name, new ArrowType.Bool()); - } else if (typeStr.equals("SMALLINT") || typeStr.equals("INT2")) { - return Field.nullable(name, new ArrowType.Int(16, true)); - } else if (typeStr.equals("INT") || typeStr.equals("INTEGER") || typeStr.equals("INT4")) { - return Field.nullable(name, new ArrowType.Int(32, true)); - } else if (typeStr.equals("BIGINT") || typeStr.equals("INT8")) { - return Field.nullable(name, new ArrowType.Int(64, true)); - } else if (typeStr.equals("FLOAT4") || typeStr.equals("REAL")) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE)); - } else if (typeStr.equals("FLOAT8") || typeStr.equals("DOUBLE PRECISION")) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE)); - } else if (typeStr.equals("DECIMAL") || typeStr.equals("NUMERIC")) { - return Field.nullable(name, new ArrowType.LargeBinary()); - } else if (typeStr.equals("DATE")) { - return Field.nullable(name, new ArrowType.Date(DateUnit.DAY)); - } else if (typeStr.equals("TIME") || typeStr.equals("TIME WITHOUT TIME ZONE")) { - return Field.nullable(name, new ArrowType.Time(TimeUnit.MICROSECOND, 64)); - } else if (typeStr.equals("TIMESTAMP") || typeStr.equals("TIMESTAMP WITHOUT TIME ZONE")) { - return Field.nullable(name, new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)); - } else if (typeStr.startsWith("INTERVAL")) { - return Field.nullable(name, new ArrowType.Interval(IntervalUnit.MONTH_DAY_NANO)); - } else if (typeStr.equals("VARCHAR")) { - return Field.nullable(name, new ArrowType.Utf8()); - } else if (typeStr.equals("JSONB")) { - return Field.nullable(name, new ArrowType.LargeUtf8()); - } else if (typeStr.equals("BYTEA")) { - return Field.nullable(name, new ArrowType.Binary()); - } else if (typeStr.endsWith("[]")) { - Field innerField = stringToField(typeStr.substring(0, typeStr.length() - 2), ""); - return new Field( - name, FieldType.nullable(new ArrowType.List()), Arrays.asList(innerField)); - } else if (typeStr.startsWith("STRUCT")) { - // extract "STRUCT" - var typeList = typeStr.substring(7, typeStr.length() - 1); - var fields = - Arrays.stream(typeList.split(",")) - .map(s -> stringToField(s.trim(), "")) - .collect(Collectors.toList()); - return new Field(name, FieldType.nullable(new ArrowType.Struct()), fields); - } else { - throw new IllegalArgumentException("Unsupported type: " + typeStr); - } - } - - /** - * Convert a Java class to an Arrow type. - * - * @param param The Java class. - * @param hint An optional DataTypeHint annotation. - * @param name The name of the field. - * @return The Arrow type. - */ - static Field classToField(Class param, DataTypeHint hint, String name) { - if (hint != null) { - return stringToField(hint.value(), name); - } else if (param == Boolean.class || param == boolean.class) { - return Field.nullable(name, new ArrowType.Bool()); - } else if (param == Short.class || param == short.class) { - return Field.nullable(name, new ArrowType.Int(16, true)); - } else if (param == Integer.class || param == int.class) { - return Field.nullable(name, new ArrowType.Int(32, true)); - } else if (param == Long.class || param == long.class) { - return Field.nullable(name, new ArrowType.Int(64, true)); - } else if (param == Float.class || param == float.class) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE)); - } else if (param == Double.class || param == double.class) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE)); - } else if (param == BigDecimal.class) { - return Field.nullable(name, new ArrowType.LargeBinary()); - } else if (param == LocalDate.class) { - return Field.nullable(name, new ArrowType.Date(DateUnit.DAY)); - } else if (param == LocalTime.class) { - return Field.nullable(name, new ArrowType.Time(TimeUnit.MICROSECOND, 64)); - } else if (param == LocalDateTime.class) { - return Field.nullable(name, new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)); - } else if (param == PeriodDuration.class) { - return Field.nullable(name, new ArrowType.Interval(IntervalUnit.MONTH_DAY_NANO)); - } else if (param == String.class) { - return Field.nullable(name, new ArrowType.Utf8()); - } else if (param == byte[].class) { - return Field.nullable(name, new ArrowType.Binary()); - } else if (param.isArray()) { - var innerField = classToField(param.getComponentType(), null, ""); - return new Field( - name, FieldType.nullable(new ArrowType.List()), Arrays.asList(innerField)); - } else { - // struct type - var fields = new ArrayList(); - for (var field : param.getDeclaredFields()) { - var subhint = field.getAnnotation(DataTypeHint.class); - fields.add(classToField(field.getType(), subhint, field.getName())); - } - return new Field(name, FieldType.nullable(new ArrowType.Struct()), fields); - // TODO: more types - // throw new IllegalArgumentException("Unsupported type: " + param); - } - } - - /** Get the input schema from a Java method. */ - static Schema methodToInputSchema(Method method) { - var fields = new ArrayList(); - for (var param : method.getParameters()) { - var hint = param.getAnnotation(DataTypeHint.class); - fields.add(classToField(param.getType(), hint, param.getName())); - } - return new Schema(fields); - } - - /** Get the output schema of a scalar function from a Java method. */ - static Schema methodToOutputSchema(Method method) { - var type = method.getReturnType(); - var hint = method.getAnnotation(DataTypeHint.class); - return new Schema(Arrays.asList(classToField(type, hint, ""))); - } - - /** Get the output schema of a table function from a Java class. */ - static Schema tableFunctionToOutputSchema(Method method) { - var hint = method.getAnnotation(DataTypeHint.class); - var type = method.getReturnType(); - if (!Iterator.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Table function must return Iterator"); - } - var typeArguments = - ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments(); - type = (Class) typeArguments[0]; - var rowIndex = Field.nullable("row_index", new ArrowType.Int(32, true)); - return new Schema(Arrays.asList(rowIndex, classToField(type, hint, ""))); - } - - /** Return functions to process input values from a Java method. */ - static Function[] methodToProcessInputs(Method method) { - var schema = methodToInputSchema(method); - var params = method.getParameters(); - @SuppressWarnings("unchecked") - Function[] funcs = new Function[schema.getFields().size()]; - for (int i = 0; i < schema.getFields().size(); i++) { - funcs[i] = processFunc(schema.getFields().get(i), params[i].getType()); - } - return funcs; - } - - /** Create an Arrow vector from an array of values. */ - static FieldVector createVector(Field field, BufferAllocator allocator, Object[] values) { - var vector = field.createVector(allocator); - fillVector(vector, values); - return vector; - } - - /** Fill an Arrow vector with an array of values. */ - static void fillVector(FieldVector fieldVector, Object[] values) { - if (fieldVector instanceof BitVector) { - var vector = (BitVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (boolean) values[i] ? 1 : 0); - } - } - } else if (fieldVector instanceof SmallIntVector) { - var vector = (SmallIntVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (short) values[i]); - } - } - } else if (fieldVector instanceof IntVector) { - var vector = (IntVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (int) values[i]); - } - } - } else if (fieldVector instanceof BigIntVector) { - var vector = (BigIntVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (long) values[i]); - } - } - } else if (fieldVector instanceof Float4Vector) { - var vector = (Float4Vector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (float) values[i]); - } - } - } else if (fieldVector instanceof Float8Vector) { - var vector = (Float8Vector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (double) values[i]); - } - } - } else if (fieldVector instanceof LargeVarBinaryVector) { - var vector = (LargeVarBinaryVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - // use `toPlainString` to avoid scientific notation - vector.set(i, ((BigDecimal) values[i]).toPlainString().getBytes()); - } - } - } else if (fieldVector instanceof DateDayVector) { - var vector = (DateDayVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (int) ((LocalDate) values[i]).toEpochDay()); - } - } - } else if (fieldVector instanceof TimeMicroVector) { - var vector = (TimeMicroVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, ((LocalTime) values[i]).toNanoOfDay() / 1000); - } - } - } else if (fieldVector instanceof TimeStampMicroVector) { - var vector = (TimeStampMicroVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, timestampToMicros((LocalDateTime) values[i])); - } - } - } else if (fieldVector instanceof IntervalMonthDayNanoVector) { - var vector = (IntervalMonthDayNanoVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - var pd = (PeriodDuration) values[i]; - var months = (int) pd.getPeriod().toTotalMonths(); - var days = pd.getPeriod().getDays(); - var nanos = pd.getDuration().toNanos(); - vector.set(i, months, days, nanos); - } - } - } else if (fieldVector instanceof VarCharVector) { - var vector = (VarCharVector) fieldVector; - int totalBytes = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - totalBytes += ((String) values[i]).length(); - } - } - vector.allocateNew(totalBytes, values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, ((String) values[i]).getBytes()); - } - } - } else if (fieldVector instanceof LargeVarCharVector) { - var vector = (LargeVarCharVector) fieldVector; - int totalBytes = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - totalBytes += ((String) values[i]).length(); - } - } - vector.allocateNew(totalBytes, values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, ((String) values[i]).getBytes()); - } - } - } else if (fieldVector instanceof VarBinaryVector) { - var vector = (VarBinaryVector) fieldVector; - int totalBytes = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - totalBytes += ((byte[]) values[i]).length; - } - } - vector.allocateNew(totalBytes, values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (byte[]) values[i]); - } - } - } else if (fieldVector instanceof ListVector) { - var vector = (ListVector) fieldVector; - vector.allocateNew(); - // flatten the `values` - var flattenLength = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] == null) { - continue; - } - var len = Array.getLength(values[i]); - vector.startNewValue(i); - vector.endValue(i, len); - flattenLength += len; - } - var flattenValues = new Object[flattenLength]; - var ii = 0; - for (var list : values) { - if (list == null) { - continue; - } - var length = Array.getLength(list); - for (int i = 0; i < length; i++) { - flattenValues[ii++] = Array.get(list, i); - } - } - // fill the inner vector - fillVector(vector.getDataVector(), flattenValues); - } else if (fieldVector instanceof StructVector) { - var vector = (StructVector) fieldVector; - vector.allocateNew(); - var lookup = MethodHandles.lookup(); - // get class of the first non-null value - Class valueClass = null; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - valueClass = values[i].getClass(); - break; - } - } - for (var field : vector.getField().getChildren()) { - // extract field from values - var subvalues = new Object[values.length]; - if (valueClass != null) { - try { - var javaField = valueClass.getDeclaredField(field.getName()); - var varHandle = lookup.unreflectVarHandle(javaField); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - subvalues[i] = varHandle.get(values[i]); - } - } - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - var subvector = vector.getChild(field.getName()); - fillVector(subvector, subvalues); - } - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.setIndexDefined(i); - } - } - } else { - throw new IllegalArgumentException("Unsupported type: " + fieldVector.getClass()); - } - fieldVector.setValueCount(values.length); - } - - static long timestampToMicros(LocalDateTime timestamp) { - var date = timestamp.toLocalDate().toEpochDay(); - var time = timestamp.toLocalTime().toNanoOfDay(); - return date * 24 * 3600 * 1000 * 1000 + time / 1000; - } - - /** Return a function that converts the object get from input array to the correct type. */ - static Function processFunc(Field field, Class targetClass) { - var inner = processFunc0(field, targetClass); - return obj -> obj == null ? null : inner.apply(obj); - } - - static Function processFunc0(Field field, Class targetClass) { - if (field.getType() instanceof ArrowType.Utf8 && targetClass == String.class) { - // object is org.apache.arrow.vector.util.Text - return obj -> obj.toString(); - } else if (field.getType() instanceof ArrowType.LargeUtf8 && targetClass == String.class) { - // object is org.apache.arrow.vector.util.Text - return obj -> obj.toString(); - } else if (field.getType() instanceof ArrowType.LargeBinary - && targetClass == BigDecimal.class) { - // object is byte[] - return obj -> new BigDecimal(new String((byte[]) obj)); - } else if (field.getType() instanceof ArrowType.Date && targetClass == LocalDate.class) { - // object is Integer - return obj -> LocalDate.ofEpochDay((int) obj); - } else if (field.getType() instanceof ArrowType.Time && targetClass == LocalTime.class) { - // object is Long - return obj -> LocalTime.ofNanoOfDay((long) obj * 1000); - } else if (field.getType() instanceof ArrowType.Interval - && targetClass == PeriodDuration.class) { - // object is arrow PeriodDuration - return obj -> new PeriodDuration((org.apache.arrow.vector.PeriodDuration) obj); - } else if (field.getType() instanceof ArrowType.List) { - // object is List - var subfield = field.getChildren().get(0); - var subfunc = processFunc(subfield, targetClass.getComponentType()); - if (subfield.getType() instanceof ArrowType.Bool) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Boolean[]::new); - } else if (subfield.getType().equals(new ArrowType.Int(16, true))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Short[]::new); - } else if (subfield.getType().equals(new ArrowType.Int(32, true))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Integer[]::new); - } else if (subfield.getType().equals(new ArrowType.Int(64, true))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Long[]::new); - } else if (subfield.getType() - .equals(new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Float[]::new); - } else if (subfield.getType() - .equals(new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Double[]::new); - } else if (subfield.getType() instanceof ArrowType.LargeBinary) { - return obj -> ((List) obj).stream().map(subfunc).toArray(BigDecimal[]::new); - } else if (subfield.getType() instanceof ArrowType.Date) { - return obj -> ((List) obj).stream().map(subfunc).toArray(LocalDate[]::new); - } else if (subfield.getType() instanceof ArrowType.Time) { - return obj -> ((List) obj).stream().map(subfunc).toArray(LocalTime[]::new); - } else if (subfield.getType() instanceof ArrowType.Timestamp) { - return obj -> ((List) obj).stream().map(subfunc).toArray(LocalDateTime[]::new); - } else if (subfield.getType() instanceof ArrowType.Interval) { - return obj -> ((List) obj).stream().map(subfunc).toArray(PeriodDuration[]::new); - } else if (subfield.getType() instanceof ArrowType.Utf8) { - return obj -> ((List) obj).stream().map(subfunc).toArray(String[]::new); - } else if (subfield.getType() instanceof ArrowType.LargeUtf8) { - return obj -> ((List) obj).stream().map(subfunc).toArray(String[]::new); - } else if (subfield.getType() instanceof ArrowType.Binary) { - return obj -> ((List) obj).stream().map(subfunc).toArray(byte[][]::new); - } else if (subfield.getType() instanceof ArrowType.Struct) { - return obj -> { - var list = (List) obj; - Object array = Array.newInstance(targetClass.getComponentType(), list.size()); - for (int i = 0; i < list.size(); i++) { - Array.set(array, i, subfunc.apply(list.get(i))); - } - return array; - }; - } - throw new IllegalArgumentException("Unsupported type: " + subfield.getType()); - } else if (field.getType() instanceof ArrowType.Struct) { - // object is org.apache.arrow.vector.util.JsonStringHashMap - var subfields = field.getChildren(); - @SuppressWarnings("unchecked") - Function[] subfunc = new Function[subfields.size()]; - for (int i = 0; i < subfields.size(); i++) { - subfunc[i] = processFunc(subfields.get(i), targetClass.getFields()[i].getType()); - } - return obj -> { - var map = (AbstractMap) obj; - try { - var row = targetClass.getDeclaredConstructor().newInstance(); - for (int i = 0; i < subfields.size(); i++) { - var field0 = targetClass.getFields()[i]; - var val = subfunc[i].apply(map.get(field0.getName())); - field0.set(row, val); - } - return row; - } catch (InstantiationException - | IllegalAccessException - | InvocationTargetException - | NoSuchMethodException e) { - throw new RuntimeException(e); - } - }; - } - return Function.identity(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/UdfProducer.java b/java/udf/src/main/java/com/risingwave/functions/UdfProducer.java deleted file mode 100644 index 692d898acaf8a..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UdfProducer.java +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import org.apache.arrow.flight.*; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorLoader; -import org.apache.arrow.vector.VectorSchemaRoot; -import org.apache.arrow.vector.VectorUnloader; -import org.apache.arrow.vector.types.pojo.Field; -import org.apache.arrow.vector.types.pojo.Schema; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class UdfProducer extends NoOpFlightProducer { - - private BufferAllocator allocator; - private HashMap functions = new HashMap<>(); - private static final Logger logger = LoggerFactory.getLogger(UdfServer.class); - - UdfProducer(BufferAllocator allocator) { - this.allocator = allocator; - } - - void addFunction(String name, UserDefinedFunction function) throws IllegalArgumentException { - UserDefinedFunctionBatch udf; - if (function instanceof ScalarFunction) { - udf = new ScalarFunctionBatch((ScalarFunction) function); - } else if (function instanceof TableFunction) { - udf = new TableFunctionBatch((TableFunction) function); - } else { - throw new IllegalArgumentException( - "Unknown function type: " + function.getClass().getName()); - } - if (functions.containsKey(name)) { - throw new IllegalArgumentException("Function already exists: " + name); - } - functions.put(name, udf); - } - - @Override - public FlightInfo getFlightInfo(CallContext context, FlightDescriptor descriptor) { - try { - var functionName = descriptor.getPath().get(0); - var udf = functions.get(functionName); - if (udf == null) { - throw new IllegalArgumentException("Unknown function: " + functionName); - } - var fields = new ArrayList(); - fields.addAll(udf.getInputSchema().getFields()); - fields.addAll(udf.getOutputSchema().getFields()); - var fullSchema = new Schema(fields); - var inputLen = udf.getInputSchema().getFields().size(); - - return new FlightInfo(fullSchema, descriptor, Collections.emptyList(), 0, inputLen); - } catch (Exception e) { - logger.error("Error occurred during getFlightInfo", e); - throw e; - } - } - - @Override - public void doExchange(CallContext context, FlightStream reader, ServerStreamListener writer) { - try (var allocator = this.allocator.newChildAllocator("exchange", 0, Long.MAX_VALUE)) { - var functionName = reader.getDescriptor().getPath().get(0); - logger.debug("call function: " + functionName); - - var udf = this.functions.get(functionName); - try (var root = VectorSchemaRoot.create(udf.getOutputSchema(), allocator)) { - var loader = new VectorLoader(root); - writer.start(root); - while (reader.next()) { - try (var input = reader.getRoot()) { - var outputBatches = udf.evalBatch(input, allocator); - while (outputBatches.hasNext()) { - try (var outputRoot = outputBatches.next()) { - var unloader = new VectorUnloader(outputRoot); - try (var outputBatch = unloader.getRecordBatch()) { - loader.load(outputBatch); - } - } - writer.putNext(); - } - } - } - writer.completed(); - } - } catch (Exception e) { - logger.error("Error occurred during UDF execution", e); - writer.error(e); - } - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/UdfServer.java b/java/udf/src/main/java/com/risingwave/functions/UdfServer.java deleted file mode 100644 index 66f2a8d3bb0dd..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UdfServer.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import java.io.IOException; -import org.apache.arrow.flight.*; -import org.apache.arrow.memory.RootAllocator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** A server that exposes user-defined functions over Apache Arrow Flight. */ -public class UdfServer implements AutoCloseable { - - private FlightServer server; - private UdfProducer producer; - private static final Logger logger = LoggerFactory.getLogger(UdfServer.class); - - public UdfServer(String host, int port) { - var location = Location.forGrpcInsecure(host, port); - var allocator = new RootAllocator(); - this.producer = new UdfProducer(allocator); - this.server = FlightServer.builder(allocator, location, this.producer).build(); - } - - /** - * Add a user-defined function to the server. - * - * @param name the name of the function - * @param udf the function to add - * @throws IllegalArgumentException if a function with the same name already exists - */ - public void addFunction(String name, UserDefinedFunction udf) throws IllegalArgumentException { - logger.info("added function: " + name); - this.producer.addFunction(name, udf); - } - - /** - * Start the server. - * - * @throws IOException if the server fails to start - */ - public void start() throws IOException { - this.server.start(); - logger.info("listening on " + this.server.getLocation().toSocketAddress()); - } - - /** - * Get the port the server is listening on. - * - * @return the port number - */ - public int getPort() { - return this.server.getPort(); - } - - /** - * Wait for the server to terminate. - * - * @throws InterruptedException if the thread is interrupted while waiting - */ - public void awaitTermination() throws InterruptedException { - this.server.awaitTermination(); - } - - /** Close the server. */ - public void close() throws InterruptedException { - this.server.close(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunction.java b/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunction.java deleted file mode 100644 index 3db6f1714cd83..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunction.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -/** - * Base interface for all user-defined functions. - * - * @see ScalarFunction - * @see TableFunction - */ -public interface UserDefinedFunction {} diff --git a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunctionBatch.java b/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunctionBatch.java deleted file mode 100644 index e2c513a7954ad..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunctionBatch.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Iterator; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; -import org.apache.arrow.vector.types.pojo.Schema; - -/** Base class for a batch-processing user-defined function. */ -abstract class UserDefinedFunctionBatch { - protected Schema inputSchema; - protected Schema outputSchema; - - /** Get the input schema of the function. */ - Schema getInputSchema() { - return inputSchema; - } - - /** Get the output schema of the function. */ - Schema getOutputSchema() { - return outputSchema; - } - - /** - * Evaluate the function by processing a batch of input data. - * - * @param batch the input data batch to process - * @param allocator the allocator to use for allocating output data - * @return an iterator over the output data batches - */ - abstract Iterator evalBatch( - VectorSchemaRoot batch, BufferAllocator allocator); -} - -/** Utility class for reflection. */ -class Reflection { - /** Get the method named eval. */ - static Method getEvalMethod(UserDefinedFunction obj) { - var methods = new ArrayList(); - for (Method method : obj.getClass().getDeclaredMethods()) { - if (method.getName().equals("eval")) { - methods.add(method); - } - } - if (methods.size() != 1) { - throw new IllegalArgumentException( - "Exactly one eval method must be defined for class " - + obj.getClass().getName()); - } - var method = methods.get(0); - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalArgumentException( - "The eval method should not be static for class " + obj.getClass().getName()); - } - return method; - } - - /** Get the method handle of the given method. */ - static MethodHandle getMethodHandle(Method method) { - var lookup = MethodHandles.lookup(); - try { - return lookup.unreflect(method); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException( - "The eval method must be public for class " - + method.getDeclaringClass().getName()); - } - } -} diff --git a/java/udf/src/test/java/com/risingwave/functions/TestUdfServer.java b/java/udf/src/test/java/com/risingwave/functions/TestUdfServer.java deleted file mode 100644 index 5722efa1dd702..0000000000000 --- a/java/udf/src/test/java/com/risingwave/functions/TestUdfServer.java +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Iterator; -import java.util.stream.IntStream; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.memory.RootAllocator; -import org.apache.arrow.vector.*; -import org.apache.arrow.vector.complex.StructVector; -import org.apache.arrow.vector.types.Types.MinorType; -import org.apache.arrow.vector.types.pojo.ArrowType; -import org.apache.arrow.vector.types.pojo.FieldType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** Unit test for UDF server. */ -public class TestUdfServer { - private static UdfClient client; - private static UdfServer server; - private static BufferAllocator allocator = new RootAllocator(); - - @BeforeAll - public static void setup() throws IOException { - server = new UdfServer("localhost", 0); - server.addFunction("gcd", new Gcd()); - server.addFunction("return_all", new ReturnAll()); - server.addFunction("series", new Series()); - server.start(); - - client = new UdfClient("localhost", server.getPort()); - } - - @AfterAll - public static void teardown() throws InterruptedException { - client.close(); - server.close(); - } - - public static class Gcd implements ScalarFunction { - public int eval(int a, int b) { - while (b != 0) { - int temp = b; - b = a % b; - a = temp; - } - return a; - } - } - - @Test - public void gcd() throws Exception { - var c0 = new IntVector("", allocator); - c0.allocateNew(1); - c0.set(0, 15); - c0.setValueCount(1); - - var c1 = new IntVector("", allocator); - c1.allocateNew(1); - c1.set(0, 12); - c1.setValueCount(1); - - var input = VectorSchemaRoot.of(c0, c1); - - try (var stream = client.call("gcd", input)) { - var output = stream.getRoot(); - assertTrue(stream.next()); - assertEquals("3", output.contentToTSVString().trim()); - } - } - - public static class ReturnAll implements ScalarFunction { - public static class Row { - public Boolean bool; - public Short i16; - public Integer i32; - public Long i64; - public Float f32; - public Double f64; - public BigDecimal decimal; - public LocalDate date; - public LocalTime time; - public LocalDateTime timestamp; - public PeriodDuration interval; - public String str; - public byte[] bytes; - public @DataTypeHint("JSONB") String jsonb; - public Struct struct; - } - - public static class Struct { - public Integer f1; - public Integer f2; - - public String toString() { - return String.format("(%d, %d)", f1, f2); - } - } - - public Row eval( - Boolean bool, - Short i16, - Integer i32, - Long i64, - Float f32, - Double f64, - BigDecimal decimal, - LocalDate date, - LocalTime time, - LocalDateTime timestamp, - PeriodDuration interval, - String str, - byte[] bytes, - @DataTypeHint("JSONB") String jsonb, - Struct struct) { - var row = new Row(); - row.bool = bool; - row.i16 = i16; - row.i32 = i32; - row.i64 = i64; - row.f32 = f32; - row.f64 = f64; - row.decimal = decimal; - row.date = date; - row.time = time; - row.timestamp = timestamp; - row.interval = interval; - row.str = str; - row.bytes = bytes; - row.jsonb = jsonb; - row.struct = struct; - return row; - } - } - - @Test - public void all_types() throws Exception { - var c0 = new BitVector("", allocator); - c0.allocateNew(2); - c0.set(0, 1); - c0.setValueCount(2); - - var c1 = new SmallIntVector("", allocator); - c1.allocateNew(2); - c1.set(0, 1); - c1.setValueCount(2); - - var c2 = new IntVector("", allocator); - c2.allocateNew(2); - c2.set(0, 1); - c2.setValueCount(2); - - var c3 = new BigIntVector("", allocator); - c3.allocateNew(2); - c3.set(0, 1); - c3.setValueCount(2); - - var c4 = new Float4Vector("", allocator); - c4.allocateNew(2); - c4.set(0, 1); - c4.setValueCount(2); - - var c5 = new Float8Vector("", allocator); - c5.allocateNew(2); - c5.set(0, 1); - c5.setValueCount(2); - - var c6 = new LargeVarBinaryVector("", allocator); - c6.allocateNew(2); - c6.set(0, "1.234".getBytes()); - c6.setValueCount(2); - - var c7 = new DateDayVector("", allocator); - c7.allocateNew(2); - c7.set(0, (int) LocalDate.of(2023, 1, 1).toEpochDay()); - c7.setValueCount(2); - - var c8 = new TimeMicroVector("", allocator); - c8.allocateNew(2); - c8.set(0, LocalTime.of(1, 2, 3).toNanoOfDay() / 1000); - c8.setValueCount(2); - - var c9 = new TimeStampMicroVector("", allocator); - c9.allocateNew(2); - var ts = LocalDateTime.of(2023, 1, 1, 1, 2, 3); - c9.set( - 0, - ts.toLocalDate().toEpochDay() * 24 * 3600 * 1000000 - + ts.toLocalTime().toNanoOfDay() / 1000); - c9.setValueCount(2); - - var c10 = - new IntervalMonthDayNanoVector( - "", - FieldType.nullable(MinorType.INTERVALMONTHDAYNANO.getType()), - allocator); - c10.allocateNew(2); - c10.set(0, 1000, 2000, 3000); - c10.setValueCount(2); - - var c11 = new VarCharVector("", allocator); - c11.allocateNew(2); - c11.set(0, "string".getBytes()); - c11.setValueCount(2); - - var c12 = new VarBinaryVector("", allocator); - c12.allocateNew(2); - c12.set(0, "bytes".getBytes()); - c12.setValueCount(2); - - var c13 = new LargeVarCharVector("", allocator); - c13.allocateNew(2); - c13.set(0, "{ key: 1 }".getBytes()); - c13.setValueCount(2); - - var c14 = - new StructVector( - "", allocator, FieldType.nullable(ArrowType.Struct.INSTANCE), null); - c14.allocateNew(); - var f1 = c14.addOrGet("f1", FieldType.nullable(MinorType.INT.getType()), IntVector.class); - var f2 = c14.addOrGet("f2", FieldType.nullable(MinorType.INT.getType()), IntVector.class); - f1.allocateNew(2); - f2.allocateNew(2); - f1.set(0, 1); - f2.set(0, 2); - c14.setIndexDefined(0); - c14.setValueCount(2); - - var input = - VectorSchemaRoot.of( - c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14); - - try (var stream = client.call("return_all", input)) { - var output = stream.getRoot(); - assertTrue(stream.next()); - assertEquals( - "{\"bool\":true,\"i16\":1,\"i32\":1,\"i64\":1,\"f32\":1.0,\"f64\":1.0,\"decimal\":\"MS4yMzQ=\",\"date\":19358,\"time\":3723000000,\"timestamp\":[2023,1,1,1,2,3],\"interval\":{\"period\":\"P1000M2000D\",\"duration\":0.000003000},\"str\":\"string\",\"bytes\":\"Ynl0ZXM=\",\"jsonb\":\"{ key: 1 }\",\"struct\":{\"f1\":1,\"f2\":2}}\n{}", - output.contentToTSVString().trim()); - } - } - - public static class Series implements TableFunction { - public Iterator eval(int n) { - return IntStream.range(0, n).iterator(); - } - } - - @Test - public void series() throws Exception { - var c0 = new IntVector("", allocator); - c0.allocateNew(3); - c0.set(0, 0); - c0.set(1, 1); - c0.set(2, 2); - c0.setValueCount(3); - - var input = VectorSchemaRoot.of(c0); - - try (var stream = client.call("series", input)) { - var output = stream.getRoot(); - assertTrue(stream.next()); - assertEquals("row_index\t\n1\t0\n2\t0\n2\t1\n", output.contentToTSVString()); - } - } -} diff --git a/java/udf/src/test/java/com/risingwave/functions/UdfClient.java b/java/udf/src/test/java/com/risingwave/functions/UdfClient.java deleted file mode 100644 index 12728bf64fbec..0000000000000 --- a/java/udf/src/test/java/com/risingwave/functions/UdfClient.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -package com.risingwave.functions; - -import org.apache.arrow.flight.*; -import org.apache.arrow.memory.RootAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; - -public class UdfClient implements AutoCloseable { - - private FlightClient client; - - public UdfClient(String host, int port) { - var allocator = new RootAllocator(); - var location = Location.forGrpcInsecure(host, port); - this.client = FlightClient.builder(allocator, location).build(); - } - - public void close() throws InterruptedException { - this.client.close(); - } - - public FlightInfo getFlightInfo(String functionName) { - var descriptor = FlightDescriptor.command(functionName.getBytes()); - return client.getInfo(descriptor); - } - - public FlightStream call(String functionName, VectorSchemaRoot root) { - var descriptor = FlightDescriptor.path(functionName); - var readerWriter = client.doExchange(descriptor); - var writer = readerWriter.getWriter(); - var reader = readerWriter.getReader(); - - writer.start(root); - writer.putNext(); - writer.completed(); - return reader; - } -} diff --git a/lints/Cargo.lock b/lints/Cargo.lock index b8fd465fecd7b..3a9bd8384c0d9 100644 --- a/lints/Cargo.lock +++ b/lints/Cargo.lock @@ -20,6 +20,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -40,9 +89,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -113,8 +162,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clippy_config" -version = "0.1.77" -source = "git+https://github.com/rust-lang/rust-clippy?rev=6fd0258e45105161b7e759a22e7350958e5cb0b1#6fd0258e45105161b7e759a22e7350958e5cb0b1" +version = "0.1.79" +source = "git+https://github.com/rust-lang/rust-clippy?rev=fca4e16ffb8c07186ee23becd44cd5c9fb51896c#fca4e16ffb8c07186ee23becd44cd5c9fb51896c" dependencies = [ "rustc-semver", "serde", @@ -123,15 +172,21 @@ dependencies = [ [[package]] name = "clippy_utils" -version = "0.1.77" -source = "git+https://github.com/rust-lang/rust-clippy?rev=6fd0258e45105161b7e759a22e7350958e5cb0b1#6fd0258e45105161b7e759a22e7350958e5cb0b1" +version = "0.1.79" +source = "git+https://github.com/rust-lang/rust-clippy?rev=fca4e16ffb8c07186ee23becd44cd5c9fb51896c#fca4e16ffb8c07186ee23becd44cd5c9fb51896c" dependencies = [ "arrayvec", "clippy_config", - "itertools 0.11.0", + "itertools", "rustc-semver", ] +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "compiletest_rs" version = "0.10.2" @@ -233,9 +288,9 @@ dependencies = [ [[package]] name = "dylint" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fdb7b800ab13925402f0048ed0911068db2e5ba6168dd93962269d4f39541d" +checksum = "4c6720f18fdd779ad137ab6bc448c11042b1b32eea625ea618c8b953660bba56" dependencies = [ "ansi_term", "anyhow", @@ -254,12 +309,13 @@ dependencies = [ [[package]] name = "dylint_internal" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5154dada2bee2a69f75f54eae57479f56f93ca1db80725a1d82cdb5fe231ef73" +checksum = "395dade88bc1a3103ef91b442498943d0072df869736a7c0107a5753c05d006f" dependencies = [ "ansi_term", "anyhow", + "bitflags 2.5.0", "cargo_metadata", "git2", "home", @@ -267,15 +323,18 @@ dependencies = [ "is-terminal", "log", "once_cell", + "regex", "rust-embed", - "sedregex", + "serde", + "thiserror", + "toml 0.8.8", ] [[package]] name = "dylint_linting" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d203baeb8770847314632f652e0e62dd7fec6a21102a116472eec0d6931f5dd9" +checksum = "3d070f934310ccf8f04a940affcce0cd196c1068b6d19c5ae6d975f926968b66" dependencies = [ "cargo_metadata", "dylint_internal", @@ -288,9 +347,9 @@ dependencies = [ [[package]] name = "dylint_testing" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1208b1f2c40fc2f3c3fa0d5631efbc7a95721d619410dc2da5b0496810d6a941" +checksum = "7fc8a64c781bde7f1e445dc1205754994012e15a2a6e659ab7166b380c1c22ab" dependencies = [ "anyhow", "cargo_metadata", @@ -310,17 +369,27 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -402,7 +471,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", "libgit2-sys", "log", @@ -489,13 +558,10 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.11.0" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" @@ -529,9 +595,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libgit2-sys" @@ -553,7 +619,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", "redox_syscall 0.4.1", ] @@ -592,7 +658,7 @@ dependencies = [ "clippy_utils", "dylint_linting", "dylint_testing", - "itertools 0.12.0", + "itertools", "thiserror-ext", "tracing", ] @@ -605,9 +671,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" @@ -690,9 +756,9 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -766,9 +832,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rust-embed" -version = "8.2.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -777,9 +843,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.2.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" dependencies = [ "proc-macro2", "quote", @@ -790,9 +856,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.2.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" dependencies = [ "globset", "sha2", @@ -819,11 +885,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -851,15 +917,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "sedregex" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19411e23596093f03bbd11dc45603b6329bb4bfec77b9fd13e2b9fc9b02efe3e" -dependencies = [ - "regex", -] - [[package]] name = "semver" version = "1.0.20" @@ -933,13 +990,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", "windows-sys 0.52.0", ] @@ -955,15 +1011,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" -dependencies = [ - "winapi-util", -] - [[package]] name = "tester" version = "0.9.1" @@ -1168,6 +1215,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1182,9 +1235,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", diff --git a/lints/Cargo.toml b/lints/Cargo.toml index 758dc2bd6d7b4..74fc49c3fd080 100644 --- a/lints/Cargo.toml +++ b/lints/Cargo.toml @@ -14,12 +14,12 @@ path = "ui/format_error.rs" # See `README.md` before bumping the version. # Remember to update the version in `ci/Dockerfile` as well. [dependencies] -clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "6fd0258e45105161b7e759a22e7350958e5cb0b1" } -dylint_linting = "2.6.0" -itertools = { workspace = true } +clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "fca4e16ffb8c07186ee23becd44cd5c9fb51896c" } +dylint_linting = "3.1.0" +itertools = "0.12" [dev-dependencies] -dylint_testing = "2.6.0" +dylint_testing = "3.1.0" # UI test dependencies anyhow = "1" diff --git a/lints/rust-toolchain b/lints/rust-toolchain index ea1f0e928e5c3..3bbdf2b2d53fd 100644 --- a/lints/rust-toolchain +++ b/lints/rust-toolchain @@ -1,5 +1,5 @@ # See `README.md` before bumping the version. [toolchain] -channel = "nightly-2024-01-11" +channel = "nightly-2024-04-18" components = ["llvm-tools-preview", "rustc-dev"] diff --git a/lints/src/format_error.rs b/lints/src/format_error.rs index 5d95921e48f38..9b6d9be5b8c4e 100644 --- a/lints/src/format_error.rs +++ b/lints/src/format_error.rs @@ -168,7 +168,7 @@ fn check_fmt_arg_in_anyhow_context(cx: &LateContext<'_>, arg_expr: &Expr<'_>) { ); } -fn check_fmt_arg_with_help(cx: &LateContext<'_>, arg_expr: &Expr<'_>, help: impl Help) { +fn check_fmt_arg_with_help(cx: &LateContext<'_>, arg_expr: &Expr<'_>, help: impl Help + 'static) { check_arg(cx, arg_expr, arg_expr.span, help); } @@ -181,7 +181,7 @@ fn check_to_string_call(cx: &LateContext<'_>, receiver: &Expr<'_>, to_string_spa ); } -fn check_arg(cx: &LateContext<'_>, arg_expr: &Expr<'_>, span: Span, help: impl Help) { +fn check_arg(cx: &LateContext<'_>, arg_expr: &Expr<'_>, span: Span, help: impl Help + 'static) { let Some(error_trait_id) = cx.tcx.get_diagnostic_item(sym::Error) else { return; }; @@ -218,31 +218,31 @@ fn check_arg(cx: &LateContext<'_>, arg_expr: &Expr<'_>, span: Span, help: impl H } trait Help { - fn normal_help(&self) -> &str; - fn anyhow_help(&self) -> &str { + fn normal_help(&self) -> &'static str; + fn anyhow_help(&self) -> &'static str { self.normal_help() } - fn report_help(&self) -> Option<&str> { + fn report_help(&self) -> Option<&'static str> { None } } -impl Help for &str { - fn normal_help(&self) -> &str { +impl Help for &'static str { + fn normal_help(&self) -> &'static str { self } } -impl Help for (&str, &str, &str) { - fn normal_help(&self) -> &str { +impl Help for (&'static str, &'static str, &'static str) { + fn normal_help(&self) -> &'static str { self.0 } - fn anyhow_help(&self) -> &str { + fn anyhow_help(&self) -> &'static str { self.1 } - fn report_help(&self) -> Option<&str> { + fn report_help(&self) -> Option<&'static str> { Some(self.2) } } diff --git a/lints/ui/format_error.rs b/lints/ui/format_error.rs index 22ea8c5f88e8b..7963a96018ba7 100644 --- a/lints/ui/format_error.rs +++ b/lints/ui/format_error.rs @@ -80,4 +80,13 @@ fn main() { let _ = anyhow!("some error occurred: {}", err.as_report()); let _ = anyhow!("{:?}", anyhow_err.as_report()); let _ = anyhow!("some error occurred: {:?}", anyhow_err.as_report()); + + let box_dyn_err_1: Box = Box::new(err.clone()); + let box_dyn_err_2: Box = Box::new(err.clone()); + let box_dyn_err_3: Box = Box::new(err.clone()); + + // TODO: fail to lint + let _ = format!("{}", box_dyn_err_1); + info!("{}", box_dyn_err_2); + let _ = box_dyn_err_3.to_string(); } diff --git a/proto/backup_service.proto b/proto/backup_service.proto index 75420149042d9..24d410b38f115 100644 --- a/proto/backup_service.proto +++ b/proto/backup_service.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package backup_service; +import "hummock.proto"; + option java_package = "com.risingwave.proto"; option optimize_for = SPEED; @@ -49,6 +51,8 @@ message MetaSnapshotMetadata { uint64 safe_epoch = 4; optional uint32 format_version = 5; optional string remarks = 6; + optional string rw_version = 7; + map state_table_info = 8; } service BackupService { diff --git a/proto/batch_plan.proto b/proto/batch_plan.proto index b6c00b1a14aa5..592b60a5ab0ac 100644 --- a/proto/batch_plan.proto +++ b/proto/batch_plan.proto @@ -7,6 +7,7 @@ import "common.proto"; import "data.proto"; import "expr.proto"; import "plan_common.proto"; +import "secret.proto"; option java_package = "com.risingwave.proto"; option optimize_for = SPEED; @@ -61,6 +62,7 @@ message SourceNode { map with_properties = 3; repeated bytes split = 4; catalog.StreamSourceInfo info = 5; + map secret_refs = 6; } message ProjectNode { @@ -71,6 +73,18 @@ message FilterNode { expr.ExprNode search_condition = 1; } +message LogRowSeqScanNode { + plan_common.StorageTableDesc table_desc = 1; + // This records the mandatory column_ids of the original table, excluding op + repeated int32 column_ids = 2; + // The partition to read for scan tasks. + // + // Will be filled by the scheduler. + common.Buffer vnode_bitmap = 3; + common.BatchQueryEpoch old_epoch = 4; + common.BatchQueryEpoch new_epoch = 5; +} + message InsertNode { // Id of the table to perform inserting. uint32 table_id = 1; @@ -213,7 +227,7 @@ message TableFunctionNode { message TaskId { string query_id = 1; uint32 stage_id = 2; - uint32 task_id = 3; + uint64 task_id = 3; } // Every task will create N buffers (channels) for parent operators to fetch results from, @@ -221,7 +235,7 @@ message TaskId { message TaskOutputId { TaskId task_id = 1; // The id of output channel to fetch from - uint32 output_id = 2; + uint64 output_id = 2; } message LocalExecutePlan { @@ -258,7 +272,7 @@ message LocalLookupJoinNode { repeated uint32 inner_side_key = 4; uint32 lookup_prefix_len = 5; plan_common.StorageTableDesc inner_side_table_desc = 6; - repeated uint32 inner_side_vnode_mapping = 7; + repeated uint64 inner_side_vnode_mapping = 7; repeated int32 inner_side_column_ids = 8; repeated uint32 output_indices = 9; repeated common.WorkerNode worker_nodes = 10; @@ -326,6 +340,7 @@ message PlanNode { SourceNode source = 34; SortOverWindowNode sort_over_window = 35; MaxOneRowNode max_one_row = 36; + LogRowSeqScanNode log_row_seq_scan = 37; // The following nodes are used for testing. bool block_executor = 100; bool busy_loop_executor = 101; diff --git a/proto/catalog.proto b/proto/catalog.proto index fd7d77116c1a3..8a360418159b3 100644 --- a/proto/catalog.proto +++ b/proto/catalog.proto @@ -6,6 +6,7 @@ import "common.proto"; import "data.proto"; import "expr.proto"; import "plan_common.proto"; +import "secret.proto"; option java_package = "com.risingwave.proto"; option optimize_for = SPEED; @@ -62,10 +63,12 @@ message StreamSourceInfo { SchemaRegistryNameStrategy name_strategy = 10; optional string key_message_name = 11; plan_common.ExternalTableDesc external_table = 12; - // **This field should now be called `is_shared`.** Not renamed for backwards compatibility. + // **This field should now be called `is_shared`.** Not renamed for backwards + // compatibility. // // Whether the stream source is a shared source (it has a streaming job). - // This is related with [RFC: Reusable Source Executor](https://github.com/risingwavelabs/rfcs/pull/72). + // This is related with [RFC: Reusable Source + // Executor](https://github.com/risingwavelabs/rfcs/pull/72). // // Currently, the following sources can be shared: // @@ -80,6 +83,10 @@ message StreamSourceInfo { bool is_distributed = 15; // Options specified by user in the FORMAT ENCODE clause. map format_encode_options = 14; + + // Handle the source relies on any sceret. The key is the propertity name and the value is the secret id and type. + // For format and encode options. + map format_encode_secret_refs = 16; } message Source { @@ -117,6 +124,9 @@ message Source { // Cluster version (tracked by git commit) when initialized/created optional string initialized_at_cluster_version = 17; optional string created_at_cluster_version = 18; + // Handle the source relies on any sceret. The key is the propertity name and the value is the secret id. + // Used for secret connect options. + map secret_refs = 19; // Per-source catalog version, used by schema change. uint64 version = 100; @@ -134,6 +144,9 @@ message SinkFormatDesc { plan_common.FormatType format = 1; plan_common.EncodeType encode = 2; map options = 3; + optional plan_common.EncodeType key_encode = 4; + // Secret used for format encode options. + map secret_refs = 5; } // the catalog of the sink. There are two kind of schema here. The full schema is all columns @@ -173,29 +186,32 @@ message Sink { // Whether it should use background ddl or block until backfill finishes. CreateType create_type = 24; + + // Handle the sink relies on any sceret. The key is the propertity name and the value is the secret id and type. + // Used for connect options. + map secret_refs = 25; } message Subscription { + enum SubscriptionState { + UNSPECIFIED = 0; + INIT = 1; + CREATED = 2; + } uint32 id = 1; string name = 2; string definition = 3; - repeated common.ColumnOrder plan_pk = 4; - repeated int32 distribution_key = 5; - map properties = 6; - repeated plan_common.ColumnCatalog column_catalogs = 7; + uint64 retention_seconds = 6; uint32 database_id = 8; uint32 schema_id = 9; - repeated uint32 dependent_relations = 10; + uint32 dependent_table_id = 10; optional uint64 initialized_at_epoch = 11; optional uint64 created_at_epoch = 12; uint32 owner = 13; - StreamJobStatus stream_job_status = 14; - optional string initialized_at_cluster_version = 15; optional string created_at_cluster_version = 16; - string subscription_from_name = 17; - optional string subscription_internal_table_name = 18; + SubscriptionState subscription_state = 19; } message Connection { @@ -233,19 +249,29 @@ message Index { // Only `InputRef` type index is supported Now. // The index of `InputRef` is the column index of the primary table. repeated expr.ExprNode index_item = 8; + repeated IndexColumnProperties index_column_properties = 16; reserved 9; // Deprecated repeated int32 original_columns = 9; optional uint64 initialized_at_epoch = 10; optional uint64 created_at_epoch = 11; StreamJobStatus stream_job_status = 12; - // Use to record the prefix len of the index_item to reconstruct index columns provided by users. + // Use to record the prefix len of the index_item to reconstruct index columns + // provided by users. uint32 index_columns_len = 13; // Cluster version (tracked by git commit) when initialized/created optional string initialized_at_cluster_version = 14; optional string created_at_cluster_version = 15; } +// https://www.postgresql.org/docs/current/functions-info.html#FUNCTIONS-INFO-INDEX-COLUMN-PROPS +message IndexColumnProperties { + // Whether the column sort in ascending(false) or descending(true) order on a forward scan. + bool is_desc = 1; + // Does the column sort with nulls first on a forward scan? + bool nulls_first = 2; +} + message Function { uint32 id = 1; uint32 schema_id = 2; @@ -319,8 +345,8 @@ message Table { // an optional column index which is the vnode of each row computed by the // table's consistent hash distribution optional uint32 vnode_col_index = 18; - // An optional column index of row id. If the primary key is specified by users, - // this will be `None`. + // An optional column index of row id. If the primary key is specified by + // users, this will be `None`. optional uint32 row_id_index = 19; // The column indices which are stored in the state store's value with // row-encoding. Currently is not supported yet and expected to be @@ -329,23 +355,26 @@ message Table { string definition = 21; // Used to control whether handling pk conflict for incoming data. HandleConflictBehavior handle_pk_conflict_behavior = 22; - // Anticipated read prefix pattern (number of fields) for the table, which can be utilized - // for implementing the table's bloom filter or other storage optimization techniques. + // Anticipated read prefix pattern (number of fields) for the table, which can + // be utilized for implementing the table's bloom filter or other storage + // optimization techniques. uint32 read_prefix_len_hint = 23; repeated int32 watermark_indices = 24; repeated int32 dist_key_in_pk = 25; - // A dml fragment id corresponds to the table, used to decide where the dml statement is executed. + // A dml fragment id corresponds to the table, used to decide where the dml + // statement is executed. optional uint32 dml_fragment_id = 26; // The range of row count of the table. - // This field is not always present due to backward compatibility. Use `Cardinality::unknown` in this case. + // This field is not always present due to backward compatibility. Use + // `Cardinality::unknown` in this case. plan_common.Cardinality cardinality = 27; optional uint64 initialized_at_epoch = 28; optional uint64 created_at_epoch = 29; - // This field is introduced in v1.2.0. It is used to indicate whether the table should use - // watermark_cache to avoid state cleaning as a performance optimization. - // In older versions we can just initialize without it. + // This field is introduced in v1.2.0. It is used to indicate whether the + // table should use watermark_cache to avoid state cleaning as a performance + // optimization. In older versions we can just initialize without it. bool cleaned_by_watermark = 30; // Used to filter created / creating tables in meta. @@ -363,14 +392,18 @@ message Table { optional string initialized_at_cluster_version = 35; optional string created_at_cluster_version = 36; - // TTL of the record in the table, to ensure the consistency with other tables in the streaming plan, it only applies to append-only tables. + // TTL of the record in the table, to ensure the consistency with other tables + // in the streaming plan, it only applies to append-only tables. optional uint32 retention_seconds = 37; - // This field specifies the index of the column set in the "with version column" within all the columns. It is used for filtering during "on conflict" operations. + // This field specifies the index of the column set in the "with version + // column" within all the columns. It is used for filtering during "on + // conflict" operations. optional uint32 version_column_index = 38; - // Per-table catalog version, used by schema change. `None` for internal tables and tests. - // Not to be confused with the global catalog version for notification service. + // Per-table catalog version, used by schema change. `None` for internal + // tables and tests. Not to be confused with the global catalog version for + // notification service. TableVersion version = 100; } @@ -415,3 +448,13 @@ message Comment { optional uint32 column_index = 4; optional string description = 5; } + +message Secret { + uint32 id = 1; + string name = 2; + uint32 database_id = 3; + // The secret here is encrypted to bytes. + bytes value = 4; + uint32 owner = 5; + uint32 schema_id = 6; +} diff --git a/proto/common.proto b/proto/common.proto index 4f0d56b4823a9..164150379c484 100644 --- a/proto/common.proto +++ b/proto/common.proto @@ -92,6 +92,12 @@ message ParallelUnitMapping { repeated uint32 data = 2; } +// Vnode mapping for stream fragments. Stores mapping from virtual node to (worker id, slot index). +message WorkerSlotMapping { + repeated uint32 original_indices = 1; + repeated uint64 data = 2; +} + message BatchQueryEpoch { oneof epoch { uint64 committed = 1; diff --git a/proto/connector_service.proto b/proto/connector_service.proto index 4deb0d6fb6096..da2c2b88087ea 100644 --- a/proto/connector_service.proto +++ b/proto/connector_service.proto @@ -4,7 +4,6 @@ package connector_service; import "catalog.proto"; import "common.proto"; -import "data.proto"; import "plan_common.proto"; option java_outer_classname = "ConnectorServiceProto"; @@ -30,34 +29,21 @@ message SinkParam { string sink_name = 8; } -enum SinkPayloadFormat { - FORMAT_UNSPECIFIED = 0; - JSON = 1; - STREAM_CHUNK = 2; -} - message SinkWriterStreamRequest { message StartSink { SinkParam sink_param = 1; - SinkPayloadFormat format = 2; + // Deprecated: SinkPayloadFormat format = 2; + reserved "format"; + reserved 2; TableSchema payload_schema = 3; } message WriteBatch { - message JsonPayload { - message RowOp { - data.Op op_type = 1; - string line = 2; - } - repeated RowOp row_ops = 1; - } - message StreamChunkPayload { bytes binary_data = 1; } oneof payload { - JsonPayload json_payload = 1; StreamChunkPayload stream_chunk_payload = 2; // This is a reference pointer to a StreamChunk. The StreamChunk is owned // by the JniSinkWriterStreamRequest, which should handle the release of StreamChunk. @@ -65,6 +51,10 @@ message SinkWriterStreamRequest { int64 stream_chunk_ref_pointer = 5; } + // Deprecated in oneof payload: JsonPayload json_payload = 1; + reserved "json_payload"; + reserved 1; + uint64 batch_id = 3; uint64 epoch = 4; } diff --git a/proto/ddl_service.proto b/proto/ddl_service.proto index 80c46dd676a13..7c6a078ce9ce4 100644 --- a/proto/ddl_service.proto +++ b/proto/ddl_service.proto @@ -100,7 +100,6 @@ message DropSinkResponse { message CreateSubscriptionRequest { catalog.Subscription subscription = 1; - stream_plan.StreamFragmentGraph fragment_graph = 2; } message CreateSubscriptionResponse { @@ -325,6 +324,7 @@ message ReplaceTablePlan { catalog.ColIndexMapping table_col_index_mapping = 3; // Source catalog of table's associated source catalog.Source source = 4; + TableJobType job_type = 5; } message ReplaceTablePlanRequest { @@ -358,6 +358,26 @@ message GetDdlProgressResponse { repeated DdlProgress ddl_progress = 1; } +message CreateSecretRequest { + string name = 1; + bytes value = 2; + uint32 database_id = 3; + uint32 schema_id = 4; + uint32 owner_id = 5; +} + +message CreateSecretResponse { + uint64 version = 1; +} + +message DropSecretRequest { + uint32 secret_id = 1; +} + +message DropSecretResponse { + uint64 version = 1; +} + message CreateConnectionRequest { message PrivateLink { catalog.Connection.PrivateLinkService.PrivateLinkProvider provider = 1; @@ -428,6 +448,8 @@ service DdlService { rpc CreateMaterializedView(CreateMaterializedViewRequest) returns (CreateMaterializedViewResponse); rpc DropMaterializedView(DropMaterializedViewRequest) returns (DropMaterializedViewResponse); rpc CreateTable(CreateTableRequest) returns (CreateTableResponse); + rpc CreateSecret(CreateSecretRequest) returns (CreateSecretResponse); + rpc DropSecret(DropSecretRequest) returns (DropSecretResponse); rpc AlterName(AlterNameRequest) returns (AlterNameResponse); rpc AlterSource(AlterSourceRequest) returns (AlterSourceResponse); rpc AlterOwner(AlterOwnerRequest) returns (AlterOwnerResponse); diff --git a/proto/expr.proto b/proto/expr.proto index c8a9887c1663e..3bb54fae6f0bb 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -194,6 +194,8 @@ message ExprNode { ENCRYPT = 325; INET_ATON = 328; INET_NTOA = 329; + QUOTE_LITERAL = 330; + QUOTE_NULLABLE = 331; // Unary operators NEG = 401; @@ -278,11 +280,13 @@ message ExprNode { JSONB_PATH_QUERY_FIRST = 623; JSONB_POPULATE_RECORD = 629; JSONB_TO_RECORD = 630; + JSONB_SET = 631; // Non-pure functions below (> 1000) // ------------------------ // Internal functions VNODE = 1101; + TEST_PAID_TIER = 1102; // Non-deterministic functions PROCTIME = 2023; PG_SLEEP = 2024; @@ -299,6 +303,10 @@ message ExprNode { PG_INDEXES_SIZE = 2404; PG_RELATION_SIZE = 2405; PG_GET_SERIAL_SEQUENCE = 2406; + PG_INDEX_COLUMN_HAS_PROPERTY = 2410; + HAS_TABLE_PRIVILEGE = 2407; + HAS_ANY_COLUMN_PRIVILEGE = 2408; + HAS_SCHEMA_PRIVILEGE = 2409; // EXTERNAL ICEBERG_TRANSFORM = 2201; @@ -325,6 +333,7 @@ message TableFunction { GENERATE_SUBSCRIPTS = 5; // buf:lint:ignore ENUM_VALUE_UPPER_SNAKE_CASE _PG_EXPANDARRAY = 6; + PG_GET_KEYWORDS = 18; // Jsonb functions JSONB_ARRAY_ELEMENTS = 10; JSONB_ARRAY_ELEMENTS_TEXT = 11; @@ -335,13 +344,13 @@ message TableFunction { JSONB_POPULATE_RECORDSET = 16; JSONB_TO_RECORDSET = 17; // User defined table function - UDTF = 100; + USER_DEFINED = 100; } Type function_type = 1; repeated expr.ExprNode args = 2; data.DataType return_type = 3; - // optional. only used when the type is UDTF. - UserDefinedTableFunction udtf = 4; + // optional. only used when the type is USER_DEFINED. + UserDefinedFunctionMetadata udf = 4; } // Reference to an upstream column, containing its index and data type. @@ -423,6 +432,8 @@ message AggCall { LAST_VALUE = 25; GROUPING = 26; INTERNAL_LAST_SEEN_VALUE = 27; + // user defined aggregate function + USER_DEFINED = 100; } Type type = 1; repeated InputRef args = 2; @@ -431,6 +442,8 @@ message AggCall { repeated common.ColumnOrder order_by = 5; ExprNode filter = 6; repeated Constant direct_args = 7; + // optional. only used when the type is USER_DEFINED. + UserDefinedFunctionMetadata udf = 8; } message WindowFrame { @@ -441,6 +454,7 @@ message WindowFrame { TYPE_ROWS = 5; TYPE_RANGE = 10; + TYPE_SESSION = 15; } enum BoundType { BOUND_TYPE_UNSPECIFIED = 0; @@ -486,6 +500,13 @@ message WindowFrame { BoundType type = 1; optional data.Datum offset = 3; } + message SessionFrameBounds { + data.Datum gap = 1; + + data.DataType order_data_type = 10; + common.OrderType order_type = 15; + data.DataType gap_data_type = 20; + } Type type = 1; @@ -497,6 +518,7 @@ message WindowFrame { oneof bounds { RowsFrameBounds rows = 10; RangeFrameBounds range = 15; + SessionFrameBounds session = 20; } } @@ -523,7 +545,7 @@ message WindowFunction { } // Note: due to historic reasons, UserDefinedFunction is a oneof variant parallel to FunctionCall, -// while UserDefinedTableFunction is embedded as a field in TableFunction. +// while UserDefinedFunctionMetadata is embedded as a field in TableFunction and AggCall. message UserDefinedFunction { repeated ExprNode children = 1; @@ -549,8 +571,8 @@ message UserDefinedFunction { optional string function_type = 12; } -// Additional information for user defined table functions. -message UserDefinedTableFunction { +// Additional information for user defined table/aggregate functions. +message UserDefinedFunctionMetadata { repeated string arg_names = 8; repeated data.DataType arg_types = 3; string language = 4; diff --git a/proto/hummock.proto b/proto/hummock.proto index dc376cd694b8b..149944831a4f9 100644 --- a/proto/hummock.proto +++ b/proto/hummock.proto @@ -69,25 +69,28 @@ message IntraLevelDelta { enum CompatibilityVersion { VERSION_UNSPECIFIED = 0; NO_TRIVIAL_SPLIT = 1; + NO_MEMBER_TABLE_IDS = 2; } message GroupConstruct { CompactionConfig group_config = 1; // If parent_group_id is not 0, it means parent_group_id splits into parent_group_id and this group, so this group is not empty initially. uint64 parent_group_id = 2; - repeated uint32 table_ids = 3; + repeated uint32 table_ids = 3 [deprecated = true]; uint64 group_id = 4; uint64 new_sst_start_id = 5; CompatibilityVersion version = 6; } message GroupMetaChange { - repeated uint32 table_ids_add = 1; - repeated uint32 table_ids_remove = 2; + option deprecated = true; + repeated uint32 table_ids_add = 1 [deprecated = true]; + repeated uint32 table_ids_remove = 2 [deprecated = true]; } message GroupTableChange { - repeated uint32 table_ids = 1; + option deprecated = true; + repeated uint32 table_ids = 1 [deprecated = true]; uint64 target_group_id = 2; uint64 origin_group_id = 3; uint64 new_sst_start_id = 4; @@ -102,7 +105,7 @@ message GroupDelta { GroupConstruct group_construct = 2; GroupDestroy group_destroy = 3; GroupMetaChange group_meta_change = 4; - GroupTableChange group_table_change = 5; + GroupTableChange group_table_change = 5 [deprecated = true]; } } @@ -149,13 +152,25 @@ message TableChangeLog { repeated EpochNewChangeLog change_logs = 1; } +message StateTableInfo { + uint64 committed_epoch = 1; + uint64 safe_epoch = 2; + uint64 compaction_group_id = 3; +} + +message StateTableInfoDelta { + uint64 committed_epoch = 1; + uint64 safe_epoch = 2; + uint64 compaction_group_id = 3; +} + message HummockVersion { message Levels { repeated Level levels = 1; OverlappingLevel l0 = 2; uint64 group_id = 3; uint64 parent_group_id = 4; - repeated uint32 member_table_ids = 5; + repeated uint32 member_table_ids = 5 [deprecated = true]; } uint64 id = 1; // Levels of each compaction group @@ -166,6 +181,7 @@ message HummockVersion { uint64 safe_epoch = 4; map table_watermarks = 5; map table_change_logs = 6; + map state_table_info = 7; } message HummockVersionDelta { @@ -185,6 +201,13 @@ message HummockVersionDelta { reserved "gc_object_ids"; map new_table_watermarks = 8; repeated uint32 removed_table_ids = 9; + message ChangeLogDelta { + EpochNewChangeLog new_log = 1; + // only logs in epoch later than truncate_epoch will be preserved + uint64 truncate_epoch = 2; + } + map change_log_delta = 10; + map state_table_info_delta = 11; } message HummockVersionDeltas { @@ -322,6 +345,7 @@ message CompactTask { JOIN_HANDLE_FAILED = 11; TRACK_SST_OBJECT_ID_FAILED = 12; NO_AVAIL_CPU_RESOURCE_CANCELED = 13; + HEARTBEAT_PROGRESS_CANCELED = 14; } // SSTs to be compacted, which will be removed from LSM after compaction repeated InputLevel input_ssts = 1; @@ -376,6 +400,8 @@ message CompactTask { map table_watermarks = 24; // The table schemas that are at least as new as the one used to create `input_ssts`. map table_schemas = 25; + // Max sub compaction task numbers + uint32 max_sub_compaction = 26; } message LevelHandler { @@ -663,6 +689,11 @@ message RiseCtlListCompactionGroupResponse { } message RiseCtlUpdateCompactionConfigRequest { + message CompressionAlgorithm { + uint32 level = 1; + string compression_algorithm = 2; + } + message MutableConfig { oneof mutable_config { uint64 max_bytes_for_level_base = 1; @@ -680,6 +711,8 @@ message RiseCtlUpdateCompactionConfigRequest { uint64 level0_max_compact_file_number = 14; bool enable_emergency_picker = 15; uint32 tombstone_reclaim_ratio = 16; + CompressionAlgorithm compression_algorithm = 17; + uint32 max_l0_compact_level_count = 18; } } repeated uint64 compaction_group_ids = 1; @@ -827,6 +860,7 @@ service HummockManagerService { rpc ListCompactTaskAssignment(ListCompactTaskAssignmentRequest) returns (ListCompactTaskAssignmentResponse); rpc ListCompactTaskProgress(ListCompactTaskProgressRequest) returns (ListCompactTaskProgressResponse); rpc CancelCompactTask(CancelCompactTaskRequest) returns (CancelCompactTaskResponse); + rpc ListChangeLogEpochs(ListChangeLogEpochsRequest) returns (ListChangeLogEpochsResponse); } message CompactionConfig { @@ -858,6 +892,9 @@ message CompactionConfig { uint32 level0_overlapping_sub_level_compact_level_count = 18; uint32 tombstone_reclaim_ratio = 19; bool enable_emergency_picker = 20; + + // The limitation of the level count of l0 compaction + uint32 max_l0_compact_level_count = 21; } message TableStats { @@ -882,7 +919,17 @@ message WriteLimits { message BranchedObject { uint64 object_id = 1; - uint64 sst_id = 2; + repeated uint64 sst_id = 2; // Compaction group id the SST belongs to. uint64 compaction_group_id = 3; } + +message ListChangeLogEpochsRequest { + uint32 table_id = 1; + uint64 min_epoch = 2; + uint32 max_count = 3; +} + +message ListChangeLogEpochsResponse { + repeated uint64 epochs = 1; +} diff --git a/proto/java_binding.proto b/proto/java_binding.proto index 32ed2f5df1992..72558438d1761 100644 --- a/proto/java_binding.proto +++ b/proto/java_binding.proto @@ -34,4 +34,5 @@ message ReadPlan { catalog.Table table_catalog = 7; repeated uint32 vnode_ids = 8; + bool use_new_object_prefix_strategy = 9; } diff --git a/proto/meta.proto b/proto/meta.proto index dadc5b364c623..0d2a1b8832915 100644 --- a/proto/meta.proto +++ b/proto/meta.proto @@ -111,6 +111,16 @@ message FragmentParallelUnitMappings { repeated FragmentParallelUnitMapping mappings = 1; } +/// Worker slot mapping with fragment id, used for notification. +message FragmentWorkerSlotMapping { + uint32 fragment_id = 1; + common.WorkerSlotMapping mapping = 2; +} + +message FragmentWorkerSlotMappings { + repeated FragmentWorkerSlotMapping mappings = 1; +} + // TODO: remove this when dashboard refactored. message ActorLocation { common.WorkerNode node = 1; @@ -254,6 +264,7 @@ enum ThrottleTarget { THROTTLE_TARGET_UNSPECIFIED = 0; SOURCE = 1; MV = 2; + TABLE_WITH_SOURCE = 3; } message ApplyThrottleRequest { @@ -377,8 +388,10 @@ message SubscribeRequest { message MetaSnapshot { message SnapshotVersion { uint64 catalog_version = 1; - uint64 parallel_unit_mapping_version = 2; + reserved 2; + reserved "parallel_unit_mapping_version"; uint64 worker_node_version = 3; + uint64 streaming_worker_slot_mapping_version = 4; } repeated catalog.Database databases = 1; repeated catalog.Schema schemas = 2; @@ -391,16 +404,21 @@ message MetaSnapshot { repeated catalog.Connection connections = 17; repeated catalog.Subscription subscriptions = 19; repeated user.UserInfo users = 8; + reserved 9; + reserved "parallel_unit_mappings"; GetSessionParamsResponse session_params = 20; - // for streaming - repeated FragmentParallelUnitMapping parallel_unit_mappings = 9; + repeated catalog.Secret secrets = 23; repeated common.WorkerNode nodes = 10; hummock.HummockSnapshot hummock_snapshot = 11; hummock.HummockVersion hummock_version = 12; backup_service.MetaBackupManifestId meta_backup_manifest_id = 14; hummock.WriteLimits hummock_write_limits = 16; - // for serving - repeated FragmentParallelUnitMapping serving_parallel_unit_mappings = 18; + reserved 18; + reserved "serving_parallel_unit_mappings"; + + // for streaming + repeated FragmentWorkerSlotMapping streaming_worker_slot_mappings = 21; + repeated FragmentWorkerSlotMapping serving_worker_slot_mappings = 22; SnapshotVersion version = 13; } @@ -439,8 +457,6 @@ message SubscribeResponse { catalog.Function function = 6; user.UserInfo user = 11; SetSessionParamRequest session_param = 26; - // for streaming - FragmentParallelUnitMapping parallel_unit_mapping = 12; common.WorkerNode node = 13; hummock.HummockSnapshot hummock_snapshot = 14; hummock.HummockVersionDeltas hummock_version_deltas = 15; @@ -450,10 +466,16 @@ message SubscribeResponse { hummock.WriteLimits hummock_write_limits = 20; RelationGroup relation_group = 21; catalog.Connection connection = 22; - FragmentParallelUnitMappings serving_parallel_unit_mappings = 23; hummock.HummockVersionStats hummock_stats = 24; Recovery recovery = 25; + FragmentWorkerSlotMapping streaming_worker_slot_mapping = 27; + FragmentWorkerSlotMappings serving_worker_slot_mappings = 28; + catalog.Secret secret = 29; } + reserved 12; + reserved "parallel_unit_mapping"; + reserved 23; + reserved "serving_parallel_unit_mappings"; } service NotificationService { @@ -507,40 +529,9 @@ message TableParallelism { } } -message GetReschedulePlanRequest { - uint64 revision = 1; - - message WorkerChanges { - repeated uint32 include_worker_ids = 1; - repeated uint32 exclude_worker_ids = 2; - optional uint32 target_parallelism = 3; - optional uint32 target_parallelism_per_worker = 4; - } - - message StableResizePolicy { - map fragment_worker_changes = 1; - } - - oneof policy { - // The StableResizePolicy will generate a stable ReschedulePlan, without altering the distribution on WorkerId that's not involved. - // Note that this "Stable" doesn't refer to the "determinacy" of the algorithm. - // Multiple repeated calls may yield different ReschedulePlan results. - StableResizePolicy stable_resize_policy = 2; - } -} - -message GetReschedulePlanResponse { - uint64 revision = 1; - // reschedule plan for each fragment - map reschedules = 2; - // todo, refactor needed - bool success = 3; -} - service ScaleService { rpc GetClusterInfo(GetClusterInfoRequest) returns (GetClusterInfoResponse); rpc Reschedule(RescheduleRequest) returns (RescheduleResponse); - rpc GetReschedulePlan(GetReschedulePlanRequest) returns (GetReschedulePlanResponse); } message MembersRequest {} @@ -580,6 +571,8 @@ message SystemParams { optional bool pause_on_next_bootstrap = 13; optional string wasm_storage_url = 14 [deprecated = true]; optional bool enable_tracing = 15; + optional bool use_new_object_prefix_strategy = 16; + optional string license_key = 17; } message GetSystemParamsRequest {} @@ -628,8 +621,10 @@ service SessionParamService { message GetServingVnodeMappingsRequest {} message GetServingVnodeMappingsResponse { - repeated FragmentParallelUnitMapping mappings = 1; + reserved 1; + reserved "mappings"; map fragment_to_table = 2; + repeated FragmentWorkerSlotMapping worker_slot_mappings = 3; } service ServingService { diff --git a/proto/monitor_service.proto b/proto/monitor_service.proto index 6a531e1bbab93..031eca4942eed 100644 --- a/proto/monitor_service.proto +++ b/proto/monitor_service.proto @@ -12,6 +12,8 @@ message StackTraceResponse { map rpc_traces = 2; map compaction_task_traces = 3; map inflight_barrier_traces = 4; + map barrier_worker_state = 5; // key: worker id + map jvm_stack_traces = 6; // key: worker id. Might be empty if the worker doesn't run JVM. } // CPU profiling @@ -63,6 +65,17 @@ message GetBackPressureResponse { repeated BackPressureInfo back_pressure_infos = 1; } +message TieredCacheTracingRequest { + bool enable = 1; + optional uint32 record_hybrid_insert_threshold_ms = 2; + optional uint32 record_hybrid_get_threshold_ms = 3; + optional uint32 record_hybrid_obtain_threshold_ms = 4; + optional uint32 record_hybrid_remove_threshold_ms = 5; + optional uint32 record_hybrid_fetch_threshold_ms = 6; +} + +message TieredCacheTracingResponse {} + service MonitorService { rpc StackTrace(StackTraceRequest) returns (StackTraceResponse); rpc Profiling(ProfilingRequest) returns (ProfilingResponse); @@ -70,4 +83,5 @@ service MonitorService { rpc ListHeapProfiling(ListHeapProfilingRequest) returns (ListHeapProfilingResponse); rpc AnalyzeHeap(AnalyzeHeapRequest) returns (AnalyzeHeapResponse); rpc GetBackPressure(GetBackPressureRequest) returns (GetBackPressureResponse); + rpc TieredCacheTracing(TieredCacheTracingRequest) returns (TieredCacheTracingResponse); } diff --git a/proto/plan_common.proto b/proto/plan_common.proto index bdd05f680a72c..a99ecde38375d 100644 --- a/proto/plan_common.proto +++ b/proto/plan_common.proto @@ -5,6 +5,7 @@ package plan_common; import "common.proto"; import "data.proto"; import "expr.proto"; +import "secret.proto"; option java_package = "com.risingwave.proto"; option optimize_for = SPEED; @@ -108,6 +109,7 @@ message ExternalTableDesc { map connect_properties = 6; // upstream cdc source job id uint32 source_id = 7; + map secret_refs = 8; } enum JoinType { @@ -147,7 +149,8 @@ enum EncodeType { ENCODE_TYPE_BYTES = 6; ENCODE_TYPE_TEMPLATE = 7; ENCODE_TYPE_NONE = 8; - ENCODE_TYPE_PARQUET = 9; + ENCODE_TYPE_TEXT = 9; + ENCODE_TYPE_PARQUET = 10; } enum RowFormatType { @@ -203,6 +206,15 @@ message AdditionalColumnHeader { data.DataType data_type = 2; } +// metadata column for cdc table +message AdditionalDatabaseName {} + +message AdditionalSchemaName {} + +message AdditionalTableName {} + +message AdditionalCollectionName {} + // this type means we read all headers as a whole message AdditionalColumnHeaders {} @@ -215,6 +227,10 @@ message AdditionalColumn { AdditionalColumnHeader header_inner = 5; AdditionalColumnFilename filename = 6; AdditionalColumnHeaders headers = 7; + AdditionalDatabaseName database_name = 8; + AdditionalSchemaName schema_name = 9; + AdditionalTableName table_name = 10; + AdditionalCollectionName collection_name = 11; } } diff --git a/proto/secret.proto b/proto/secret.proto new file mode 100644 index 0000000000000..8e4e1b228d6c5 --- /dev/null +++ b/proto/secret.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package secret; + +message SecretMetaBackend { + bytes value = 1; +} + +message SecretHashicropValutBackend { + string host = 1; + string vault_token = 2; +} + +message Secret { + // the message is stored in meta as encrypted bytes and is interpreted as bytes by catalog + oneof secret_backend { + SecretMetaBackend meta = 1; + SecretHashicropValutBackend hashicorp_vault = 2; + } +} + +message SecretRef { + enum RefAsType { + UNSPECIFIED = 0; + TEXT = 1; + // AS FILE + FILE = 2; + } + uint32 secret_id = 1; + RefAsType ref_as = 2; +} diff --git a/proto/stream_plan.proto b/proto/stream_plan.proto index 89c0521378972..3469851c566b7 100644 --- a/proto/stream_plan.proto +++ b/proto/stream_plan.proto @@ -7,6 +7,7 @@ import "common.proto"; import "data.proto"; import "expr.proto"; import "plan_common.proto"; +import "secret.proto"; import "source.proto"; option java_package = "com.risingwave.proto"; @@ -94,6 +95,16 @@ message CombinedMutation { repeated BarrierMutation mutations = 1; } +message CreateSubscriptionMutation { + uint32 subscription_id = 1; + uint32 upstream_mv_table_id = 2; +} + +message DropSubscriptionMutation { + uint32 subscription_id = 1; + uint32 upstream_mv_table_id = 2; +} + message BarrierMutation { oneof mutation { // Add new dispatchers to some actors, used for creating materialized views. @@ -111,6 +122,10 @@ message BarrierMutation { ResumeMutation resume = 8; // Throttle specific source exec or chain exec. ThrottleMutation throttle = 10; + // Create subscription on mv + CreateSubscriptionMutation create_subscription = 11; + // Drop subscription on mv + DropSubscriptionMutation drop_subscription = 12; // Combined mutation. CombinedMutation combined = 100; } @@ -174,6 +189,7 @@ message StreamSource { string source_name = 8; // Streaming rate limit optional uint32 rate_limit = 9; + map secret_refs = 10; } // copy contents from StreamSource to prevent compatibility issues in the future @@ -189,6 +205,7 @@ message StreamFsFetch { string source_name = 8; // Streaming rate limit optional uint32 rate_limit = 9; + map secret_refs = 10; } // The executor only for receiving barrier from the meta service. It always resides in the leaves @@ -219,6 +236,7 @@ message SourceBackfillNode { // `| partition_id | backfill_progress |` catalog.Table state_table = 8; + map secret_refs = 9; } message SinkDesc { @@ -240,6 +258,7 @@ message SinkDesc { catalog.SinkFormatDesc format_desc = 13; optional uint32 target_table = 14; optional uint64 extra_partition_col_idx = 15; + map secret_refs = 16; } enum SinkLogStoreType { @@ -264,12 +283,20 @@ message ProjectNode { repeated uint32 watermark_input_cols = 2; repeated uint32 watermark_output_cols = 3; repeated uint32 nondecreasing_exprs = 4; + // Whether there are likely no-op updates in the output chunks, so that eliminating them with + // `StreamChunk::eliminate_adjacent_noop_update` could be beneficial. + bool noop_update_hint = 5; } message FilterNode { expr.ExprNode search_condition = 1; } +message ChangeLogNode { + // Whether or not there is an op in the final output. + bool need_op = 1; +} + message CdcFilterNode { expr.ExprNode search_condition = 1; uint32 upstream_source_id = 2; @@ -561,6 +588,16 @@ message StreamScanNode { catalog.Table arrangement_table = 10; } +// Config options for CDC backfill +message StreamCdcScanOptions { + // Whether skip the backfill and only consume from upstream. + bool disable_backfill = 1; + + uint32 snapshot_barrier_interval = 2; + + uint32 snapshot_batch_size = 3; +} + message StreamCdcScanNode { uint32 table_id = 1; @@ -581,7 +618,10 @@ message StreamCdcScanNode { optional uint32 rate_limit = 6; // Whether skip the backfill and only consume from upstream. + // keep it for backward compatibility, new stream plan will use `options.disable_backfill` bool disable_backfill = 7; + + StreamCdcScanOptions options = 8; } // BatchPlanNode is used for mv on mv snapshot read. @@ -695,9 +735,21 @@ message RowIdGenNode { uint64 row_id_index = 1; } +message NowModeUpdateCurrent {} + +message NowModeGenerateSeries { + data.Datum start_timestamp = 1; + data.Datum interval = 2; +} + message NowNode { // Persists emitted 'now'. catalog.Table state_table = 1; + + oneof mode { + NowModeUpdateCurrent update_current = 101; + NowModeGenerateSeries generate_series = 102; + } } message ValuesNode { @@ -738,12 +790,6 @@ message OverWindowNode { OverWindowCachePolicy cache_policy = 5; } -message SubscriptionNode { - catalog.Subscription subscription_catalog = 1; - // log store should have a table. - catalog.Table log_store_table = 2; -} - message StreamNode { oneof node_body { SourceNode source = 100; @@ -787,8 +833,8 @@ message StreamNode { StreamFsFetchNode stream_fs_fetch = 138; StreamCdcScanNode stream_cdc_scan = 139; CdcFilterNode cdc_filter = 140; - SubscriptionNode subscription = 141; SourceBackfillNode source_backfill = 142; + ChangeLogNode changelog = 143; } // The id for the operator. This is local per mview. // TODO: should better be a uint32. @@ -884,7 +930,6 @@ enum FragmentTypeFlag { FRAGMENT_TYPE_FLAG_VALUES = 64; FRAGMENT_TYPE_FLAG_DML = 128; FRAGMENT_TYPE_FLAG_CDC_FILTER = 256; - FRAGMENT_TYPE_FLAG_SUBSCRIPTION = 512; FRAGMENT_TYPE_FLAG_SOURCE_SCAN = 1024; } diff --git a/proto/stream_service.proto b/proto/stream_service.proto index 5990fe1e2cbcf..85b12d8ed5fa1 100644 --- a/proto/stream_service.proto +++ b/proto/stream_service.proto @@ -9,10 +9,18 @@ import "stream_plan.proto"; option java_package = "com.risingwave.proto"; option optimize_for = SPEED; +message BuildActorInfo { + stream_plan.StreamActor actor = 1; + message SubscriptionIds { + repeated uint32 subscription_ids = 1; + } + map related_subscriptions = 2; +} + // Describe the fragments which will be running on this node message UpdateActorsRequest { string request_id = 1; - repeated stream_plan.StreamActor actors = 2; + repeated BuildActorInfo actors = 2; } message UpdateActorsResponse { @@ -49,6 +57,7 @@ message InjectBarrierRequest { stream_plan.Barrier barrier = 2; repeated uint32 actor_ids_to_send = 3; repeated uint32 actor_ids_to_collect = 4; + repeated uint32 table_ids_to_sync = 5; } message BarrierCompleteResponse { @@ -69,6 +78,7 @@ message BarrierCompleteResponse { repeated GroupedSstableInfo synced_sstables = 4; uint32 worker_id = 5; map table_watermarks = 6; + repeated hummock.SstableInfo old_value_sstables = 7; } // Before starting streaming, the leader node broadcast the actor-host table to needed workers. diff --git a/proto/telemetry.proto b/proto/telemetry.proto index 9890f48250538..a3c4ab7ef9dfa 100644 --- a/proto/telemetry.proto +++ b/proto/telemetry.proto @@ -19,6 +19,14 @@ enum TelemetryNodeType { TELEMETRY_NODE_TYPE_COMPACTOR = 4; } +enum TelemetryClusterType { + TELEMETRY_CLUSTER_TYPE_UNSPECIFIED = 0; + TELEMETRY_CLUSTER_TYPE_SINGLE_NODE = 1; + TELEMETRY_CLUSTER_TYPE_DOCKER_COMPOSE = 2; + TELEMETRY_CLUSTER_TYPE_KUBERNETES = 3; + TELEMETRY_CLUSTER_TYPE_CLOUD_HOSTED = 4; +} + message SystemMemory { uint64 used = 1; uint64 total = 2; @@ -88,6 +96,12 @@ message MetaReport { // stream_jobs is the list of running streaming jobs // and is used to collect the table_id, connector_name and table_optimizations repeated StreamJobDesc stream_jobs = 6; + + // How the cluster is deployed + TelemetryClusterType cluster_type = 7; + + // The object store media type obtained from ObjectStore::store_media_type + string object_store_media_type = 8; } enum PlanOptimization { diff --git a/proto/user.proto b/proto/user.proto index 383e78cb57b28..6218fea4fd98d 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -66,7 +66,7 @@ message GrantPrivilege { uint32 all_tables_schema_id = 11; uint32 all_sources_schema_id = 12; - uint32 all_dml_tables_schema_id = 13; + uint32 all_dml_relations_schema_id = 13; uint32 subscription_id = 14; } repeated ActionWithGrantOption action_with_opts = 7; diff --git a/risedev.yml b/risedev.yml index 964a00bf99e1f..cdc115ca30010 100644 --- a/risedev.yml +++ b/risedev.yml @@ -43,9 +43,6 @@ profile: # - use: compactor # If you want to create source from Kafka, uncomment the following lines - # Note that kafka depends on zookeeper, so zookeeper must be started beforehand. - # - use: zookeeper - # persist-data: true # - use: kafka # persist-data: true @@ -108,15 +105,14 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor - use: prometheus - use: grafana - # - use: zookeeper - # persist-data: true - # - use: kafka - # persist-data: true + - use: kafka + persist-data: true standalone-full-peripherals: steps: @@ -124,6 +120,7 @@ profile: - use: etcd - use: meta-node user-managed: true + meta-backend: etcd - use: compute-node user-managed: true - use: frontend @@ -132,8 +129,6 @@ profile: user-managed: true - use: prometheus - use: grafana - - use: zookeeper - persist-data: true - use: kafka persist-data: true @@ -143,6 +138,7 @@ profile: - use: etcd - use: meta-node user-managed: true + meta-backend: etcd - use: compute-node user-managed: true - use: frontend @@ -156,6 +152,7 @@ profile: - use: etcd - use: meta-node user-managed: true + meta-backend: etcd - use: compute-node user-managed: true - use: frontend @@ -260,6 +257,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -268,8 +266,6 @@ profile: remote-write-region: "ap-southeast-1" remote-write-url: "https://aps-workspaces.ap-southeast-1.amazonaws.com/workspaces/ws-f3841dad-6a5c-420f-8f62-8f66487f512a/api/v1/remote_write" - use: grafana - - use: zookeeper - persist-data: true - use: kafka persist-data: true @@ -295,6 +291,7 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: etcd - use: meta-node port: 15690 dashboard-port: 15691 @@ -342,6 +339,7 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: etcd - use: meta-node port: 15690 dashboard-port: 15691 @@ -362,6 +360,7 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: sqlite - use: compactor - use: compute-node - use: frontend @@ -375,6 +374,75 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: sqlite + - use: compactor + - use: compute-node + - use: frontend + + meta-1cn-1fe-pg-backend: + steps: + - use: minio + - use: postgres + port: 8432 + user: postgres + database: metadata + - use: meta-node + port: 5690 + dashboard-port: 5691 + exporter-port: 1250 + meta-backend: postgres + - use: compactor + - use: compute-node + - use: frontend + + meta-1cn-1fe-pg-backend-with-recovery: + config-path: src/config/ci-recovery.toml + steps: + - use: minio + - use: postgres + port: 8432 + user: postgres + database: metadata + - use: meta-node + port: 5690 + dashboard-port: 5691 + exporter-port: 1250 + meta-backend: postgres + - use: compactor + - use: compute-node + - use: frontend + + meta-1cn-1fe-mysql-backend: + steps: + - use: minio + - use: mysql + port: 4306 + user: root + database: metadata + application: metastore + - use: meta-node + port: 5690 + dashboard-port: 5691 + exporter-port: 1250 + meta-backend: mysql + - use: compactor + - use: compute-node + - use: frontend + + meta-1cn-1fe-mysql-backend-with-recovery: + config-path: src/config/ci-recovery.toml + steps: + - use: minio + - use: mysql + port: 4306 + user: root + database: metadata + application: metastore + - use: meta-node + port: 5690 + dashboard-port: 5691 + exporter-port: 1250 + meta-backend: mysql - use: compactor - use: compute-node - use: frontend @@ -401,16 +469,15 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node parallelism: 8 - use: frontend - use: compactor - use: prometheus - use: grafana - # Do not use kafka and zookeeper here, we will spawn it separately, + # Do not use kafka here, we will spawn it separately, # so we don't have to re-generate data each time. - # - use: zookeeper - # persist-data: true # RW will still be ale to talk to it. # - use: kafka # port: 9092 @@ -561,6 +628,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -573,6 +641,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -595,6 +664,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -621,6 +691,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -645,6 +716,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -669,6 +741,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -728,6 +801,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: opendal engine: fs bucket: "/tmp/rw_ci" @@ -761,6 +835,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -805,13 +880,35 @@ profile: health-check-port: 6788 - use: compactor - ci-pubsub-kafka: + ci-kafka: config-path: src/config/ci.toml steps: - use: minio - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd + - use: compute-node + enable-tiered-cache: true + - use: frontend + - use: compactor + - use: kafka + user-managed: true + address: message_queue + port: 29092 + - use: schema-registry + user-managed: true + address: schemaregistry + port: 8082 + + ci-inline-source-test: + config-path: src/config/ci-recovery.toml + steps: + - use: minio + - use: etcd + unsafe-no-fsync: true + - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -822,6 +919,16 @@ profile: user-managed: true address: message_queue port: 29092 + - use: schema-registry + user-managed: true + address: schemaregistry + port: 8082 + - use: mysql + port: 3306 + address: mysql + user: root + password: 123456 + user-managed: true ci-redis: config-path: src/config/ci.toml @@ -830,6 +937,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -843,6 +951,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true total-memory-bytes: 17179869184 @@ -855,6 +964,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -867,6 +977,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -882,37 +993,56 @@ profile: - use: frontend - use: compactor - ci-1cn-1fe-kafka-with-recovery: + ci-1cn-1fe-user-kafka-with-recovery: config-path: src/config/ci-recovery.toml steps: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend - use: compactor - - use: zookeeper - persist-data: true - use: kafka - persist-data: true + user-managed: true + address: message_queue + port: 29092 - ci-meta-backup-test: + ci-meta-backup-test-etcd: config-path: src/config/ci-meta-backup-test.toml steps: - use: etcd - use: minio - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor - ci-meta-backup-test-restore: + ci-meta-backup-test-restore-etcd: config-path: src/config/ci-meta-backup-test.toml steps: - use: etcd - use: minio + ci-meta-backup-test-sql: + config-path: src/config/ci-meta-backup-test.toml + steps: + - use: sqlite + - use: minio + - use: meta-node + meta-backend: sqlite + - use: compute-node + - use: frontend + - use: compactor + + ci-meta-backup-test-restore-sql: + config-path: src/config/ci-meta-backup-test.toml + steps: + - use: sqlite + - use: minio + ci-meta-etcd-for-migration: config-path: src/config/ci.toml steps: @@ -953,6 +1083,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -964,6 +1095,7 @@ profile: bucket: renjie-iceberg-bench - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -976,6 +1108,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -1136,12 +1269,21 @@ template: # If `user-managed` is true, this service will be started by user with the above config user-managed: false + # meta backend type, requires extra config for provided backend + meta-backend: "memory" + # Etcd backend config provide-etcd-backend: "etcd*" # Sqlite backend config provide-sqlite-backend: "sqlite*" + # Postgres backend config + provide-postgres-backend: "postgres*" + + # Mysql backend config + provide-mysql-backend: "mysql*" + # Prometheus nodes used by dashboard service provide-prometheus: "prometheus*" @@ -1324,7 +1466,7 @@ template: # access key, secret key and region should be set in aws config (either by env var or .aws/config) - # Apache Kafka service + # Apache Kafka service backed by docker. kafka: # Id to be picked-up by services id: kafka-${port} @@ -1335,45 +1477,47 @@ template: # Listen port of Kafka port: 29092 - # Listen address - listen-address: ${address} + # Listen port of KRaft controller + controller-port: 29093 + # Listen port for other services in docker (schema-registry) + docker-port: 29094 - # ZooKeeper used by this Kafka instance - provide-zookeeper: "zookeeper*" + # The docker image. Can be overridden to use a different version. + image: "confluentinc/cp-kafka:7.6.1" # If set to true, data will be persisted at data/{id}. persist-data: true - # Kafka broker id. If there are multiple instances of Kafka, we will need to set. - broker-id: 0 + # Kafka node id. If there are multiple instances of Kafka, we will need to set. + node-id: 0 user-managed: false - # Google pubsub emulator service - pubsub: - id: pubsub-${port} + schema-registry: + # Id to be picked-up by services + id: schema-registry-${port} + # Advertise address address: "127.0.0.1" - port: 5980 + # Listen port of Schema Registry + port: 8081 - persist-data: true + # The docker image. Can be overridden to use a different version. + image: "confluentinc/cp-schema-registry:7.6.1" - # Apache ZooKeeper service - zookeeper: - # Id to be picked-up by services - id: zookeeper-${port} + user-managed: false - # Advertise address of ZooKeeper - address: "127.0.0.1" + provide-kafka: "kafka*" - # Listen address - listen-address: ${address} + # Google pubsub emulator service + pubsub: + id: pubsub-${port} - # Listen port of ZooKeeper - port: 2181 + address: "127.0.0.1" + + port: 5980 - # If set to true, data will be persisted at data/{id}. persist-data: true # Only supported in RiseDev compose @@ -1406,3 +1550,71 @@ template: # address of redis address: "127.0.0.1" + + # MySQL service backed by docker. + mysql: + # Id to be picked-up by services + id: mysql-${port} + + # address of mysql + address: "127.0.0.1" + + # listen port of mysql + port: 8306 + + # Note: + # - This will be used to initialize the MySQL instance. + # * If the user is "root", the password will be used as the root password. + # * Otherwise, a regular user will be created with the given password. The root password will be empty. + # Note that this only applies to fresh instances, i.e., the data directory is empty. + # - In user-managed mode, these configs are not validated by risedev. + # They are passed as-is to risedev-env default user for MySQL operations. + # - This is not used in RISEDEV_MYSQL_WITH_OPTIONS_COMMON. + user: root + password: "" + database: "risedev" + + # Which application to use. Can be overridden for metastore purpose. + application: "connector" + + # The docker image. Can be overridden to use a different version. + image: "mysql:8.0" + + # If set to true, data will be persisted at data/{id}. + persist-data: true + + # If `user-managed` is true, user is responsible for starting the service + # to serve at the above address and port in any way they see fit. + user-managed: false + + # PostgreSQL service backed by docker. + postgres: + # Id to be picked-up by services + id: postgres-${port} + + # address of pg + address: "127.0.0.1" + + # listen port of pg + port: 8432 + + # Note: + # - This will be used to initialize the PostgreSQL instance if it's fresh. + # - In user-managed mode, these configs are not validated by risedev. + # They are passed as-is to risedev-env default user for PostgreSQL operations. + user: postgres + password: "" + database: "postgres" + + # Which application to use. Can be overridden for connector purpose. + application: "metastore" + + # The docker image. Can be overridden to use a different version. + image: "postgres:15-alpine" + + # If set to true, data will be persisted at data/{id}. + persist-data: true + + # If `user-managed` is true, user is responsible for starting the service + # to serve at the above address and port in any way they see fit. + user-managed: false diff --git a/scripts/install/install-risingwave.sh b/scripts/install/install-risingwave.sh index e2095593d597e..b610ea6365cb0 100755 --- a/scripts/install/install-risingwave.sh +++ b/scripts/install/install-risingwave.sh @@ -11,7 +11,7 @@ META_STORE_PATH="${HOME}/.risingwave/meta_store" VERSION=$(curl -s https://api.github.com/repos/risingwavelabs/risingwave/releases/latest \ | grep '.tag_name' \ - | sed -E -n 's/.*(v[0-9]+.[0-9]+.[0-9])\",/\1/p') + | sed -E -n 's/.*(v[0-9]+.[0-9]+.[0-9]+.*)\",/\1/p') BASE_URL="https://github.com/risingwavelabs/risingwave/releases/download" @@ -48,11 +48,11 @@ fi ############# BREW INSTALL if [ "${USE_BREW}" -eq 1 ]; then - echo "Installing RisingWave@${VERSION} using Homebrew." + echo "Installing RisingWave using Homebrew." brew tap risingwavelabs/risingwave brew install risingwave echo - echo "Successfully installed RisingWave@${VERSION} using Homebrew." + echo "Successfully installed RisingWave using Homebrew." echo echo "Run RisingWave:" echo @@ -97,7 +97,7 @@ echo " rm -r ~/.risingwave" echo echo "To view available options, run:" echo -echo " risingwave single-node --help" +echo " ./risingwave single-node --help" echo # Check if $JAVA_HOME is set, if not, prompt user to install Java, and set $JAVA_HOME. if [ -z "${JAVA_HOME}" ]; then diff --git a/scripts/source/README.md b/scripts/source/README.md index 4676aa7723b37..311c058a8230b 100644 --- a/scripts/source/README.md +++ b/scripts/source/README.md @@ -3,3 +3,10 @@ This folder contains scripts to prepare data for testing sources. ## Kafka `scripts/source/test_data` contains the data. Filename's convention is `.`. + +- If `` ends with `bin`, the whole file is a message with binary data. +- If `` ends with `avro_json` or `json_schema`: + - The first line is the schema. Key and value are separated by `^`. + - The rest of the lines are messages in JSON format. Key and value are separated by `^`. + - Produced to Kafka with `schema_registry_producer.py` (serialized to Avro or JSON) +- Otherwise, each line is a message, and key/value is separated by `^`. diff --git a/scripts/source/prepare_ci_kafka.sh b/scripts/source/prepare_ci_kafka.sh index c3d72cca3d250..27c88e6e63af2 100755 --- a/scripts/source/prepare_ci_kafka.sh +++ b/scripts/source/prepare_ci_kafka.sh @@ -5,9 +5,12 @@ set -e SCRIPT_PATH="$(cd "$(dirname "$0")" >/dev/null 2>&1 && pwd)" cd "$SCRIPT_PATH/.." || exit 1 +# cwd is /scripts echo "$SCRIPT_PATH" +source ../.risingwave/config/risedev-env + if [ "$1" == "compress" ]; then echo "Compress test_data/ into test_data.zip" cd ./source @@ -37,15 +40,14 @@ for filename in $kafka_data_files; do # always ok echo "Drop topic $topic" - risedev kafka-topics --topic "$topic" --delete || true + risedev rpk topic delete "$topic" || true echo "Recreate topic $topic with partition $partition" - risedev kafka-topics --topic "$topic" --create --partitions "$partition") & + risedev rpk topic create "$topic" --partitions "$partition") & done wait echo "Fulfill kafka topics" -python3 -m pip install --break-system-packages requests fastavro confluent_kafka jsonschema for filename in $kafka_data_files; do ([ -e "$filename" ] base=$(basename "$filename") @@ -54,27 +56,13 @@ for filename in $kafka_data_files; do echo "Fulfill kafka topic $topic with data from $base" # binary data, one message a file, filename/topic ends with "bin" if [[ "$topic" = *bin ]]; then - kcat -P -b message_queue:29092 -t "$topic" "$filename" + kcat -P -b "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" -t "$topic" "$filename" elif [[ "$topic" = *avro_json ]]; then - python3 source/schema_registry_producer.py "message_queue:29092" "http://message_queue:8081" "$filename" "topic" "avro" + python3 source/schema_registry_producer.py "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" "${RISEDEV_SCHEMA_REGISTRY_URL}" "$filename" "topic" "avro" elif [[ "$topic" = *json_schema ]]; then - python3 source/schema_registry_producer.py "kafka:9093" "http://schemaregistry:8082" "$filename" "topic" "json" + python3 source/schema_registry_producer.py "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" "${RISEDEV_SCHEMA_REGISTRY_URL}" "$filename" "topic" "json" else - cat "$filename" | kcat -P -K ^ -b message_queue:29092 -t "$topic" + cat "$filename" | kcat -P -K ^ -b "${RISEDEV_KAFKA_BOOTSTRAP_SERVERS}" -t "$topic" fi ) & done - -# test additional columns: produce messages with headers -ADDI_COLUMN_TOPIC="kafka_additional_columns" -for i in {0..100}; do echo "key$i:{\"a\": $i}" | kcat -P -b message_queue:29092 -t ${ADDI_COLUMN_TOPIC} -K : -H "header1=v1" -H "header2=v2"; done - -# write schema with name strategy - -## topic: upsert_avro_json-record, key subject: string, value subject: CPLM.OBJ_ATTRIBUTE_VALUE -(python3 source/schema_registry_producer.py "message_queue:29092" "http://message_queue:8081" source/test_data/upsert_avro_json.1 "record" "avro") & -## topic: upsert_avro_json-topic-record, -## key subject: upsert_avro_json-topic-record-string -## value subject: upsert_avro_json-topic-record-CPLM.OBJ_ATTRIBUTE_VALUE -(python3 source/schema_registry_producer.py "message_queue:29092" "http://message_queue:8081" source/test_data/upsert_avro_json.1 "topic-record" "avro") & -wait diff --git a/scripts/source/prepare_ci_pubsub/Cargo.toml b/scripts/source/prepare_ci_pubsub/Cargo.toml deleted file mode 100644 index ff3fd2b311468..0000000000000 --- a/scripts/source/prepare_ci_pubsub/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "prepare_ci_pubsub" -version = "0.1.0" -edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[package.metadata.cargo-machete] -ignored = ["workspace-hack"] - -[package.metadata.cargo-udeps.ignore] -normal = ["workspace-hack"] - -[dependencies] -anyhow = "1" -google-cloud-googleapis = { version = "0.12", features = ["pubsub"] } -google-cloud-pubsub = "0.24" -tokio = { version = "0.2", package = "madsim-tokio", features = [ - "rt", - "rt-multi-thread", - "sync", - "macros", - "time", - "signal", - "fs", -] } - -[lints] -workspace = true diff --git a/scripts/source/prepare_ci_pubsub/src/main.rs b/scripts/source/prepare_ci_pubsub/src/main.rs deleted file mode 100644 index 5357e2d4b6065..0000000000000 --- a/scripts/source/prepare_ci_pubsub/src/main.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::fs::File; -use std::io::prelude::*; -use std::io::BufReader; - -use google_cloud_googleapis::pubsub::v1::PubsubMessage; -use google_cloud_pubsub::client::Client; -use google_cloud_pubsub::subscription::SubscriptionConfig; - -const TOPIC: &str = "test-topic"; - -const SUBSCRIPTION_COUNT: usize = 50; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - std::env::set_var("PUBSUB_EMULATOR_HOST", "127.0.0.1:5980"); - - let client = Client::new(Default::default()).await?; - - // delete and create "test-topic" - let topic = client.topic(TOPIC); - for subscription in topic.subscriptions(None).await? { - subscription.delete(None).await?; - } - - let _ = topic.delete(None).await; - topic.create(Some(Default::default()), None).await?; - for i in 0..SUBSCRIPTION_COUNT { - let _ = client - .create_subscription( - format!("test-subscription-{}", i).as_str(), - TOPIC, - SubscriptionConfig { - retain_acked_messages: true, - ..Default::default() - }, - None, - ) - .await?; - } - - let path = std::env::current_exe()? - .parent() - .and_then(|p| p.parent()) - .and_then(|p| p.parent()) - .unwrap() - .join("scripts/source/test_data/pubsub_1_test_topic.1"); - - let file = File::open(path)?; - let file = BufReader::new(file); - - let publisher = topic.new_publisher(Default::default()); - for line in file.lines().map_while(Result::ok) { - let a = publisher - .publish(PubsubMessage { - data: line.clone().into_bytes(), - ..Default::default() - }) - .await; - a.get().await?; - println!("published {}", line); - } - - Ok(()) -} diff --git a/scripts/source/schema_registry_producer.py b/scripts/source/schema_registry_producer.py index 79a3d4db1b40f..a88861b65bd26 100644 --- a/scripts/source/schema_registry_producer.py +++ b/scripts/source/schema_registry_producer.py @@ -39,8 +39,11 @@ def load_avro_json(encoded, schema): if __name__ == '__main__': - if len(sys.argv) < 5: - print("datagen.py ") + if len(sys.argv) <= 5: + print( + "usage: schema_registry_producer.py " + ) + exit(1) broker_list = sys.argv[1] schema_registry_url = sys.argv[2] file = sys.argv[3] diff --git a/scripts/source/test_data/avro_c_bin.1 b/scripts/source/test_data/avro_complex_schema_bin.1 similarity index 100% rename from scripts/source/test_data/avro_c_bin.1 rename to scripts/source/test_data/avro_complex_schema_bin.1 diff --git a/scripts/source/test_data/avro_bin.1 b/scripts/source/test_data/avro_simple_schema_bin.1 similarity index 100% rename from scripts/source/test_data/avro_bin.1 rename to scripts/source/test_data/avro_simple_schema_bin.1 diff --git a/scripts/source/test_data/kafka_json_schema.1 b/scripts/source/test_data/kafka_json_schema.1 index e71485885a654..afa96107864cb 100644 --- a/scripts/source/test_data/kafka_json_schema.1 +++ b/scripts/source/test_data/kafka_json_schema.1 @@ -1,2 +1,2 @@ -{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://example.com/product.schema.json","title":"Product","description":"A product from Acme's catalog","type":"object","properties":{"productId":{"description":"The unique identifier for a product","type":"integer"},"productName":{"description":"Name of the product","type":"string"},"price":{"description":"The price of the product","type":"number","exclusiveMinimum":0},"tags":{"description":"Tags for the product","type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true},"dimensions":{"type":"object","properties":{"length":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"}},"required":["length","width","height"]}},"required":["productId","productName","price"]} -{"productId":1,"productName":"An ice sculpture","price":12.5,"tags":["cold","ice"],"dimensions":{"length":7,"width":12,"height":9.5}} \ No newline at end of file +{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://example.com/product.schema.json","title":"Product","description":"A product from Acme's catalog","type":"object","properties":{"productId":{"description":"The unique identifier for a product","type":"integer"},"productName":{"description":"Name of the product","type":"string"},"price":{"description":"The price of the product","type":"number","exclusiveMinimum":0},"tags":{"description":"Tags for the product","type":"array","items":{"type":"string"},"minItems":1,"uniqueItems":true},"dimensions":{"type":"object","properties":{"length":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"}},"required":["length","width","height"]},"map":{"type":"object","additionalProperties":{"type":"string"}},"notMap":{"type":"object","additionalProperties":{"type":"string"},"properties":{"a":{"type":"string"}}}},"required":["productId","productName","price"]} +{"productId":1,"productName":"An ice sculpture","price":12.5,"tags":["cold","ice"],"dimensions":{"length":7,"width":12,"height":9.5},"map":{"foo":"bar"},"notMap":{"a":"b","ignored":"c"}} \ No newline at end of file diff --git a/scripts/source/test_data/pubsub_1_test_topic.1 b/scripts/source/test_data/pubsub_1_test_topic.1 deleted file mode 100644 index fdc299d97d826..0000000000000 --- a/scripts/source/test_data/pubsub_1_test_topic.1 +++ /dev/null @@ -1,20 +0,0 @@ -{"v1":1,"v2":"name0"} -{"v1":2,"v2":"name0"} -{"v1":6,"v2":"name3"} -{"v1":0,"v2":"name5"} -{"v1":5,"v2":"name8"} -{"v1":6,"v2":"name4"} -{"v1":8,"v2":"name9"} -{"v1":9,"v2":"name2"} -{"v1":4,"v2":"name6"} -{"v1":5,"v2":"name3"} -{"v1":8,"v2":"name8"} -{"v1":9,"v2":"name2"} -{"v1":2,"v2":"name3"} -{"v1":4,"v2":"name7"} -{"v1":7,"v2":"name0"} -{"v1":0,"v2":"name9"} -{"v1":3,"v2":"name2"} -{"v1":7,"v2":"name5"} -{"v1":1,"v2":"name7"} -{"v1":3,"v2":"name9"} diff --git a/src/batch/Cargo.toml b/src/batch/Cargo.toml index 019c33253466b..cf94b2ec838aa 100644 --- a/src/batch/Cargo.toml +++ b/src/batch/Cargo.toml @@ -16,10 +16,12 @@ normal = ["workspace-hack"] [dependencies] anyhow = "1" arrow-array = { workspace = true } +arrow-array-iceberg = { workspace = true } arrow-schema = { workspace = true } assert_matches = "1" async-recursion = "1" async-trait = "0.1" +bytes = "1" either = "1" foyer = { workspace = true } futures = { version = "0.3", default-features = false, features = ["alloc"] } @@ -27,12 +29,14 @@ futures-async-stream = { workspace = true } futures-util = "0.3" hashbrown = { workspace = true } hytra = "0.1.2" -icelake = { workspace = true } +iceberg = { workspace = true } itertools = { workspace = true } memcomparable = "0.2" +opendal = "0.47" parking_lot = { workspace = true } paste = "1" prometheus = { version = "0.13", features = ["process"] } +prost = "0.12" rand = { workspace = true } risingwave_common = { workspace = true } risingwave_common_estimate_size = { workspace = true } @@ -62,6 +66,8 @@ tokio-stream = "0.1" tokio-util = { workspace = true } tonic = { workspace = true } tracing = "0.1" +twox-hash = "1" +uuid = { version = "1", features = ["v4"] } [target.'cfg(not(madsim))'.dependencies] workspace-hack = { path = "../workspace-hack" } diff --git a/src/batch/benches/hash_agg.rs b/src/batch/benches/hash_agg.rs index a918541beea18..d86812623d422 100644 --- a/src/batch/benches/hash_agg.rs +++ b/src/batch/benches/hash_agg.rs @@ -13,10 +13,13 @@ // limitations under the License. pub mod utils; +use std::sync::Arc; + use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use itertools::Itertools; use risingwave_batch::executor::aggregation::build as build_agg; use risingwave_batch::executor::{BoxedExecutor, HashAggExecutor}; +use risingwave_batch::monitor::BatchSpillMetrics; use risingwave_batch::task::ShutdownToken; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::memory::MemoryContext; @@ -49,6 +52,7 @@ fn create_agg_call( order_by: vec![], filter: None, direct_args: vec![], + udf: None, } } @@ -95,7 +99,7 @@ fn create_hash_agg_executor( let schema = Schema { fields }; Box::new(HashAggExecutor::::new( - agg_init_states, + Arc::new(agg_init_states), group_key_columns, group_key_types, schema, @@ -103,6 +107,8 @@ fn create_hash_agg_executor( "HashAggExecutor".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), ShutdownToken::empty(), )) } diff --git a/src/batch/benches/hash_join.rs b/src/batch/benches/hash_join.rs index 723080638e670..004340b7b5715 100644 --- a/src/batch/benches/hash_join.rs +++ b/src/batch/benches/hash_join.rs @@ -18,6 +18,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; use risingwave_batch::executor::hash_join::HashJoinExecutor; use risingwave_batch::executor::test_utils::{gen_projected_data, MockExecutor}; use risingwave_batch::executor::{BoxedExecutor, JoinType}; +use risingwave_batch::monitor::BatchSpillMetrics; use risingwave_batch::task::ShutdownToken; use risingwave_common::catalog::schema_test_utils::field_n; use risingwave_common::memory::MemoryContext; @@ -61,7 +62,7 @@ fn create_hash_join_executor( _ => vec![0, 1], }; - let cond = with_cond.then(|| build_from_pretty("(greater_than:int8 $0:int8 100:int8)")); + let cond = with_cond.then(|| build_from_pretty("(greater_than:int8 $0:int8 100:int8)").into()); Box::new(HashJoinExecutor::::new( join_type, @@ -74,6 +75,8 @@ fn create_hash_join_executor( cond, "HashJoinExecutor".into(), CHUNK_SIZE, + None, + BatchSpillMetrics::for_test(), ShutdownToken::empty(), MemoryContext::none(), )) diff --git a/src/batch/benches/sort.rs b/src/batch/benches/sort.rs index a2580c2773c9a..6a45473d2369b 100644 --- a/src/batch/benches/sort.rs +++ b/src/batch/benches/sort.rs @@ -14,8 +14,11 @@ pub mod utils; +use std::sync::Arc; + use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; use risingwave_batch::executor::{BoxedExecutor, SortExecutor}; +use risingwave_batch::monitor::BatchSpillMetrics; use risingwave_common::enable_jemalloc; use risingwave_common::memory::MemoryContext; use risingwave_common::types::DataType; @@ -57,10 +60,12 @@ fn create_order_by_executor( Box::new(SortExecutor::new( child, - column_orders, + Arc::new(column_orders), "SortExecutor".into(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )) } diff --git a/src/batch/src/error.rs b/src/batch/src/error.rs index 5751ab86ea9aa..2a555f18c356a 100644 --- a/src/batch/src/error.rs +++ b/src/batch/src/error.rs @@ -116,7 +116,7 @@ pub enum BatchError { Iceberg( #[from] #[backtrace] - icelake::Error, + iceberg::Error, ), // Make the ref-counted type to be a variant for easier code structuring. @@ -136,6 +136,16 @@ pub enum BatchError { #[error("Streaming vnode mapping not found for fragment {0}")] StreamingVnodeMappingNotFound(FragmentId), + + #[error("Not enough memory to run this query, batch memory limit is {0} bytes")] + OutOfMemory(u64), + + #[error("Failed to spill out to disk")] + Spill( + #[from] + #[backtrace] + opendal::Error, + ), } // Serialize/deserialize error. diff --git a/src/batch/src/exchange_source.rs b/src/batch/src/exchange_source.rs index b602b14d5c018..409061594338d 100644 --- a/src/batch/src/exchange_source.rs +++ b/src/batch/src/exchange_source.rs @@ -15,9 +15,10 @@ use std::fmt::Debug; use std::future::Future; +use futures_async_stream::try_stream; use risingwave_common::array::DataChunk; -use crate::error::Result; +use crate::error::{BatchError, Result}; use crate::execution::grpc_exchange::GrpcExchangeSource; use crate::execution::local_exchange::LocalExchangeSource; use crate::executor::test_utils::FakeExchangeSource; @@ -54,4 +55,16 @@ impl ExchangeSourceImpl { ExchangeSourceImpl::Fake(fake) => fake.get_task_id(), } } + + #[try_stream(boxed, ok = DataChunk, error = BatchError)] + pub(crate) async fn take_data_stream(self) { + let mut source = self; + loop { + match source.take_data().await { + Ok(Some(chunk)) => yield chunk, + Ok(None) => break, + Err(e) => return Err(e), + } + } + } } diff --git a/src/batch/src/executor/aggregation/distinct.rs b/src/batch/src/executor/aggregation/distinct.rs index 6792ff7a80f69..97780271b83fd 100644 --- a/src/batch/src/executor/aggregation/distinct.rs +++ b/src/batch/src/executor/aggregation/distinct.rs @@ -16,7 +16,7 @@ use std::collections::HashSet; use std::ops::Range; use risingwave_common::array::StreamChunk; -use risingwave_common::buffer::BitmapBuilder; +use risingwave_common::bitmap::BitmapBuilder; use risingwave_common::row::{OwnedRow, Row}; use risingwave_common::types::{DataType, Datum}; use risingwave_common_estimate_size::EstimateSize; @@ -62,12 +62,12 @@ impl AggregateFunction for Distinct { self.inner.return_type() } - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::new(State { - inner: self.inner.create_state(), + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::new(State { + inner: self.inner.create_state()?, exists: HashSet::new(), exists_estimated_heap_size: 0, - })) + }))) } async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { @@ -203,7 +203,7 @@ mod tests { fn test_agg(pretty: &str, input: StreamChunk, expected: Datum) { let agg = build(&AggCall::from_pretty(pretty)).unwrap(); - let mut state = agg.create_state(); + let mut state = agg.create_state().unwrap(); agg.update(&mut state, &input) .now_or_never() .unwrap() diff --git a/src/batch/src/executor/aggregation/filter.rs b/src/batch/src/executor/aggregation/filter.rs index d7b5efc592a8f..2fbc50372a0e1 100644 --- a/src/batch/src/executor/aggregation/filter.rs +++ b/src/batch/src/executor/aggregation/filter.rs @@ -41,7 +41,7 @@ impl AggregateFunction for Filter { self.inner.return_type() } - fn create_state(&self) -> AggregateState { + fn create_state(&self) -> Result { self.inner.create_state() } @@ -86,7 +86,7 @@ mod tests { condition.into(), build_append_only(&AggCall::from_pretty("(count:int8 $0:int8)")).unwrap(), ); - let mut state = agg.create_state(); + let mut state = agg.create_state()?; let chunk = StreamChunk::from_pretty( " I @@ -115,7 +115,7 @@ mod tests { expr.into(), build_append_only(&AggCall::from_pretty("(count:int8 $0:int8)")).unwrap(), ); - let mut state = agg.create_state(); + let mut state = agg.create_state()?; let chunk = StreamChunk::from_pretty( " I @@ -147,7 +147,7 @@ mod tests { expr.into(), build_append_only(&AggCall::from_pretty("(count:int8 $0:int8)")).unwrap(), ); - let mut state = agg.create_state(); + let mut state = agg.create_state()?; let chunk = StreamChunk::from_pretty( " I diff --git a/src/batch/src/executor/aggregation/orderby.rs b/src/batch/src/executor/aggregation/orderby.rs index e9cb90eb7fd79..7f564100b8c99 100644 --- a/src/batch/src/executor/aggregation/orderby.rs +++ b/src/batch/src/executor/aggregation/orderby.rs @@ -93,11 +93,11 @@ impl AggregateFunction for ProjectionOrderBy { self.inner.return_type() } - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::new(State { + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::new(State { unordered_values: vec![], unordered_values_estimated_heap_size: 0, - })) + }))) } async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { @@ -127,7 +127,7 @@ impl AggregateFunction for ProjectionOrderBy { async fn get_result(&self, state: &AggregateState) -> Result { let state = state.downcast_ref::(); - let mut inner_state = self.inner.create_state(); + let mut inner_state = self.inner.create_state()?; // sort let mut rows = state.unordered_values.clone(); rows.sort_unstable_by(|(key_a, _), (key_b, _)| key_a.cmp(key_b)); @@ -168,7 +168,7 @@ mod tests { "(array_agg:int4[] $0:int4 orderby $1:asc $0:desc)", )) .unwrap(); - let mut state = agg.create_state(); + let mut state = agg.create_state().unwrap(); agg.update(&mut state, &chunk).await.unwrap(); assert_eq!( agg.get_result(&state).await.unwrap(), @@ -189,7 +189,7 @@ mod tests { "(string_agg:varchar $0:varchar $1:varchar orderby $2:asc $3:desc $0:desc)", )) .unwrap(); - let mut state = agg.create_state(); + let mut state = agg.create_state().unwrap(); agg.update(&mut state, &chunk).await.unwrap(); assert_eq!( agg.get_result(&state).await.unwrap(), diff --git a/src/batch/src/executor/aggregation/projection.rs b/src/batch/src/executor/aggregation/projection.rs index af2b49faf9cdb..02235163bc6b7 100644 --- a/src/batch/src/executor/aggregation/projection.rs +++ b/src/batch/src/executor/aggregation/projection.rs @@ -36,7 +36,7 @@ impl AggregateFunction for Projection { self.inner.return_type() } - fn create_state(&self) -> AggregateState { + fn create_state(&self) -> Result { self.inner.create_state() } diff --git a/src/batch/src/executor/delete.rs b/src/batch/src/executor/delete.rs index 00b7ffb1ca519..f85fad254a750 100644 --- a/src/batch/src/executor/delete.rs +++ b/src/batch/src/executor/delete.rs @@ -39,6 +39,7 @@ pub struct DeleteExecutor { table_version_id: TableVersionId, dml_manager: DmlManagerRef, child: BoxedExecutor, + #[expect(dead_code)] chunk_size: usize, schema: Schema, identity: String, @@ -195,8 +196,6 @@ mod tests { use std::sync::Arc; use futures::StreamExt; - use itertools::Itertools; - use risingwave_common::array::Array; use risingwave_common::catalog::{ schema_test_utils, ColumnDesc, ColumnId, INITIAL_TABLE_VERSION_ID, }; diff --git a/src/batch/src/executor/generic_exchange.rs b/src/batch/src/executor/generic_exchange.rs index 24ff47958dd3f..9a13fd198f9aa 100644 --- a/src/batch/src/executor/generic_exchange.rs +++ b/src/batch/src/executor/generic_exchange.rs @@ -45,6 +45,7 @@ pub struct GenericExchangeExecutor { context: C, schema: Schema, + #[expect(dead_code)] task_id: TaskId, identity: String, @@ -277,9 +278,8 @@ impl GenericExchangeExec #[cfg(test)] mod tests { - use futures::StreamExt; use rand::Rng; - use risingwave_common::array::{Array, DataChunk, I32Array}; + use risingwave_common::array::{Array, I32Array}; use risingwave_common::types::DataType; use super::*; diff --git a/src/batch/src/executor/group_top_n.rs b/src/batch/src/executor/group_top_n.rs index 2b33ac6f28319..b1f4791358131 100644 --- a/src/batch/src/executor/group_top_n.rs +++ b/src/batch/src/executor/group_top_n.rs @@ -15,7 +15,6 @@ use std::marker::PhantomData; use std::mem::swap; use std::sync::Arc; -use std::vec::Vec; use futures_async_stream::try_stream; use hashbrown::HashMap; @@ -240,11 +239,9 @@ impl GroupTopNExecutor { #[cfg(test)] mod tests { use futures::stream::StreamExt; - use risingwave_common::array::DataChunk; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::catalog::Field; use risingwave_common::metrics::LabelGuardedIntGauge; use risingwave_common::test_prelude::DataChunkTestExt; - use risingwave_common::types::DataType; use risingwave_common::util::sort_util::OrderType; use super::*; @@ -254,7 +251,7 @@ mod tests { #[tokio::test] async fn test_group_top_n_executor() { - let parent_mem = MemoryContext::root(LabelGuardedIntGauge::<4>::test_int_gauge()); + let parent_mem = MemoryContext::root(LabelGuardedIntGauge::<4>::test_int_gauge(), u64::MAX); { let schema = Schema { fields: vec![ diff --git a/src/batch/src/executor/hash_agg.rs b/src/batch/src/executor/hash_agg.rs index 402d96934a1b1..e394c9167c7f1 100644 --- a/src/batch/src/executor/hash_agg.rs +++ b/src/batch/src/executor/hash_agg.rs @@ -12,25 +12,40 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::hash::BuildHasher; use std::marker::PhantomData; +use std::sync::Arc; +use bytes::Bytes; use futures_async_stream::try_stream; +use hashbrown::hash_map::Entry; use itertools::Itertools; +use prost::Message; use risingwave_common::array::{DataChunk, StreamChunk}; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::hash::{HashKey, HashKeyDispatcher, PrecomputedBuildHasher}; use risingwave_common::memory::MemoryContext; -use risingwave_common::types::DataType; +use risingwave_common::row::{OwnedRow, Row, RowExt}; +use risingwave_common::types::{DataType, ToOwnedDatum}; +use risingwave_common::util::chunk_coalesce::DataChunkBuilder; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common_estimate_size::EstimateSize; use risingwave_expr::aggregate::{AggCall, AggregateState, BoxedAggregateFunction}; use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::HashAggNode; +use risingwave_pb::data::DataChunk as PbDataChunk; use crate::error::{BatchError, Result}; use crate::executor::aggregation::build as build_agg; use crate::executor::{ BoxedDataChunkStream, BoxedExecutor, BoxedExecutorBuilder, Executor, ExecutorBuilder, + WrapStreamExecutor, +}; +use crate::monitor::BatchSpillMetrics; +use crate::spill::spill_op::SpillBackend::Disk; +use crate::spill::spill_op::{ + SpillBackend, SpillBuildHasher, SpillOp, DEFAULT_SPILL_PARTITION_NUM, SPILL_AT_LEAST_MEMORY, }; use crate::task::{BatchTaskContext, ShutdownToken, TaskId}; @@ -42,7 +57,7 @@ impl HashKeyDispatcher for HashAggExecutorBuilder { fn dispatch_impl(self) -> Self::Output { Box::new(HashAggExecutor::::new( - self.aggs, + Arc::new(self.aggs), self.group_key_columns, self.group_key_types, self.schema, @@ -50,6 +65,8 @@ impl HashKeyDispatcher for HashAggExecutorBuilder { self.identity, self.chunk_size, self.mem_context, + self.spill_backend, + self.spill_metrics, self.shutdown_rx, )) } @@ -65,10 +82,13 @@ pub struct HashAggExecutorBuilder { group_key_types: Vec, child: BoxedExecutor, schema: Schema, + #[expect(dead_code)] task_id: TaskId, identity: String, chunk_size: usize, mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, shutdown_rx: ShutdownToken, } @@ -80,6 +100,8 @@ impl HashAggExecutorBuilder { identity: String, chunk_size: usize, mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, shutdown_rx: ShutdownToken, ) -> Result { let aggs: Vec<_> = hash_agg_node @@ -118,6 +140,8 @@ impl HashAggExecutorBuilder { identity, chunk_size, mem_context, + spill_backend, + spill_metrics, shutdown_rx, }; @@ -140,6 +164,8 @@ impl BoxedExecutorBuilder for HashAggExecutorBuilder { let identity = source.plan_node().get_identity(); + let spill_metrics = source.context.spill_metrics(); + Self::deserialize( hash_agg_node, child, @@ -147,6 +173,12 @@ impl BoxedExecutorBuilder for HashAggExecutorBuilder { identity.clone(), source.context.get_config().developer.chunk_size, source.context.create_executor_mem_context(identity), + if source.context.get_config().enable_spill { + Some(Disk) + } else { + None + }, + spill_metrics, source.shutdown_rx.clone(), ) } @@ -155,7 +187,7 @@ impl BoxedExecutorBuilder for HashAggExecutorBuilder { /// `HashAggExecutor` implements the hash aggregate algorithm. pub struct HashAggExecutor { /// Aggregate functions. - aggs: Vec, + aggs: Arc>, /// Column indexes that specify a group group_key_columns: Vec, /// Data types of group key columns @@ -163,16 +195,23 @@ pub struct HashAggExecutor { /// Output schema schema: Schema, child: BoxedExecutor, + /// Used to initialize the state of the aggregation from the spilled files. + init_agg_state_executor: Option, identity: String, chunk_size: usize, mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, + /// The upper bound of memory usage for this executor. + memory_upper_bound: Option, shutdown_rx: ShutdownToken, _phantom: PhantomData, } impl HashAggExecutor { + #[allow(clippy::too_many_arguments)] pub fn new( - aggs: Vec, + aggs: Arc>, group_key_columns: Vec, group_key_types: Vec, schema: Schema, @@ -180,6 +219,41 @@ impl HashAggExecutor { identity: String, chunk_size: usize, mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, + shutdown_rx: ShutdownToken, + ) -> Self { + Self::new_inner( + aggs, + group_key_columns, + group_key_types, + schema, + child, + None, + identity, + chunk_size, + mem_context, + spill_backend, + spill_metrics, + None, + shutdown_rx, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + aggs: Arc>, + group_key_columns: Vec, + group_key_types: Vec, + schema: Schema, + child: BoxedExecutor, + init_agg_state_executor: Option, + identity: String, + chunk_size: usize, + mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, + memory_upper_bound: Option, shutdown_rx: ShutdownToken, ) -> Self { HashAggExecutor { @@ -188,9 +262,13 @@ impl HashAggExecutor { group_key_types, schema, child, + init_agg_state_executor, identity, chunk_size, mem_context, + spill_backend, + spill_metrics, + memory_upper_bound, shutdown_rx, _phantom: PhantomData, } @@ -211,18 +289,259 @@ impl Executor for HashAggExecutor { } } +/// `AggSpillManager` is used to manage how to write spill data file and read them back. +/// The spill data first need to be partitioned. Each partition contains 2 files: `agg_state_file` and `input_chunks_file`. +/// The spill file consume a data chunk and serialize the chunk into a protobuf bytes. +/// Finally, spill file content will look like the below. +/// The file write pattern is append-only and the read pattern is sequential scan. +/// This can maximize the disk IO performance. +/// +/// ```text +/// [proto_len] +/// [proto_bytes] +/// ... +/// [proto_len] +/// [proto_bytes] +/// ``` +pub struct AggSpillManager { + op: SpillOp, + partition_num: usize, + agg_state_writers: Vec, + agg_state_chunk_builder: Vec, + input_writers: Vec, + input_chunk_builders: Vec, + spill_build_hasher: SpillBuildHasher, + group_key_types: Vec, + child_data_types: Vec, + agg_data_types: Vec, + spill_chunk_size: usize, + spill_metrics: Arc, +} + +impl AggSpillManager { + fn new( + spill_backend: SpillBackend, + agg_identity: &String, + partition_num: usize, + group_key_types: Vec, + agg_data_types: Vec, + child_data_types: Vec, + spill_chunk_size: usize, + spill_metrics: Arc, + ) -> Result { + let suffix_uuid = uuid::Uuid::new_v4(); + let dir = format!("/{}-{}/", agg_identity, suffix_uuid); + let op = SpillOp::create(dir, spill_backend)?; + let agg_state_writers = Vec::with_capacity(partition_num); + let agg_state_chunk_builder = Vec::with_capacity(partition_num); + let input_writers = Vec::with_capacity(partition_num); + let input_chunk_builders = Vec::with_capacity(partition_num); + // Use uuid to generate an unique hasher so that when recursive spilling happens they would use a different hasher to avoid data skew. + let spill_build_hasher = SpillBuildHasher(suffix_uuid.as_u64_pair().1); + Ok(Self { + op, + partition_num, + agg_state_writers, + agg_state_chunk_builder, + input_writers, + input_chunk_builders, + spill_build_hasher, + group_key_types, + child_data_types, + agg_data_types, + spill_chunk_size, + spill_metrics, + }) + } + + async fn init_writers(&mut self) -> Result<()> { + for i in 0..self.partition_num { + let agg_state_partition_file_name = format!("agg-state-p{}", i); + let w = self.op.writer_with(&agg_state_partition_file_name).await?; + self.agg_state_writers.push(w); + + let partition_file_name = format!("input-chunks-p{}", i); + let w = self.op.writer_with(&partition_file_name).await?; + self.input_writers.push(w); + self.input_chunk_builders.push(DataChunkBuilder::new( + self.child_data_types.clone(), + self.spill_chunk_size, + )); + self.agg_state_chunk_builder.push(DataChunkBuilder::new( + self.group_key_types + .iter() + .cloned() + .chain(self.agg_data_types.iter().cloned()) + .collect(), + self.spill_chunk_size, + )); + } + Ok(()) + } + + async fn write_agg_state_row(&mut self, row: impl Row, hash_code: u64) -> Result<()> { + let partition = hash_code as usize % self.partition_num; + if let Some(output_chunk) = self.agg_state_chunk_builder[partition].append_one_row(row) { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.agg_state_writers[partition].write(len_bytes).await?; + self.agg_state_writers[partition].write(buf).await?; + } + Ok(()) + } + + async fn write_input_chunk(&mut self, chunk: DataChunk, hash_codes: Vec) -> Result<()> { + let (columns, vis) = chunk.into_parts_v2(); + for partition in 0..self.partition_num { + let new_vis = vis.clone() + & Bitmap::from_iter( + hash_codes + .iter() + .map(|hash_code| (*hash_code as usize % self.partition_num) == partition), + ); + let new_chunk = DataChunk::from_parts(columns.clone(), new_vis); + for output_chunk in self.input_chunk_builders[partition].append_chunk(new_chunk) { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.input_writers[partition].write(len_bytes).await?; + self.input_writers[partition].write(buf).await?; + } + } + Ok(()) + } + + async fn close_writers(&mut self) -> Result<()> { + for partition in 0..self.partition_num { + if let Some(output_chunk) = self.agg_state_chunk_builder[partition].consume_all() { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.agg_state_writers[partition].write(len_bytes).await?; + self.agg_state_writers[partition].write(buf).await?; + } + + if let Some(output_chunk) = self.input_chunk_builders[partition].consume_all() { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.input_writers[partition].write(len_bytes).await?; + self.input_writers[partition].write(buf).await?; + } + } + + for mut w in self.agg_state_writers.drain(..) { + w.close().await?; + } + for mut w in self.input_writers.drain(..) { + w.close().await?; + } + Ok(()) + } + + async fn read_agg_state_partition(&mut self, partition: usize) -> Result { + let agg_state_partition_file_name = format!("agg-state-p{}", partition); + let r = self.op.reader_with(&agg_state_partition_file_name).await?; + Ok(SpillOp::read_stream(r, self.spill_metrics.clone())) + } + + async fn read_input_partition(&mut self, partition: usize) -> Result { + let input_partition_file_name = format!("input-chunks-p{}", partition); + let r = self.op.reader_with(&input_partition_file_name).await?; + Ok(SpillOp::read_stream(r, self.spill_metrics.clone())) + } + + async fn estimate_partition_size(&self, partition: usize) -> Result { + let agg_state_partition_file_name = format!("agg-state-p{}", partition); + let agg_state_size = self + .op + .stat(&agg_state_partition_file_name) + .await? + .content_length(); + let input_partition_file_name = format!("input-chunks-p{}", partition); + let input_size = self + .op + .stat(&input_partition_file_name) + .await? + .content_length(); + Ok(agg_state_size + input_size) + } + + async fn clear_partition(&mut self, partition: usize) -> Result<()> { + let agg_state_partition_file_name = format!("agg-state-p{}", partition); + self.op.delete(&agg_state_partition_file_name).await?; + let input_partition_file_name = format!("input-chunks-p{}", partition); + self.op.delete(&input_partition_file_name).await?; + Ok(()) + } +} + impl HashAggExecutor { #[try_stream(boxed, ok = DataChunk, error = BatchError)] async fn do_execute(self: Box) { + let child_schema = self.child.schema().clone(); + let mut need_to_spill = false; + // If the memory upper bound is less than 1MB, we don't need to check memory usage. + let check_memory = match self.memory_upper_bound { + Some(upper_bound) => upper_bound > SPILL_AT_LEAST_MEMORY, + None => true, + }; + // hash map for each agg groups let mut groups = AggHashMap::::with_hasher_in( PrecomputedBuildHasher, self.mem_context.global_allocator(), ); + if let Some(init_agg_state_executor) = self.init_agg_state_executor { + // `init_agg_state_executor` exists which means this is a sub `HashAggExecutor` used to consume spilling data. + // The spilled agg states by its parent executor need to be recovered first. + let mut init_agg_state_stream = init_agg_state_executor.execute(); + #[for_await] + for chunk in &mut init_agg_state_stream { + let chunk = chunk?; + let group_key_indices = (0..self.group_key_columns.len()).collect_vec(); + let keys = K::build_many(&group_key_indices, &chunk); + let mut memory_usage_diff = 0; + for (row_id, key) in keys.into_iter().enumerate() { + let mut agg_states = vec![]; + for i in 0..self.aggs.len() { + let agg = &self.aggs[i]; + let datum = chunk + .row_at(row_id) + .0 + .datum_at(self.group_key_columns.len() + i) + .to_owned_datum(); + let agg_state = agg.decode_state(datum)?; + memory_usage_diff += agg_state.estimated_size() as i64; + agg_states.push(agg_state); + } + groups.try_insert(key, agg_states).unwrap(); + } + + if !self.mem_context.add(memory_usage_diff) && check_memory { + warn!("not enough memory to load one partition agg state after spill which is not a normal case, so keep going"); + } + } + } + + let mut input_stream = self.child.execute(); // consume all chunks to compute the agg result #[for_await] - for chunk in self.child.execute() { + for chunk in &mut input_stream { let chunk = StreamChunk::from(chunk?); let keys = K::build_many(self.group_key_columns.as_slice(), &chunk); let mut memory_usage_diff = 0; @@ -235,10 +554,18 @@ impl HashAggExecutor { continue; } let mut new_group = false; - let states = groups.entry(key).or_insert_with(|| { - new_group = true; - self.aggs.iter().map(|agg| agg.create_state()).collect() - }); + let states = match groups.entry(key) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + new_group = true; + let states = self + .aggs + .iter() + .map(|agg| agg.create_state()) + .try_collect()?; + entry.insert(states) + } + }; // TODO: currently not a vectorized implementation for (agg, state) in self.aggs.iter().zip_eq_fast(states) { @@ -250,52 +577,168 @@ impl HashAggExecutor { } } // update memory usage - self.mem_context.add(memory_usage_diff); + if !self.mem_context.add(memory_usage_diff) && check_memory { + if self.spill_backend.is_some() { + need_to_spill = true; + break; + } else { + Err(BatchError::OutOfMemory(self.mem_context.mem_limit()))?; + } + } } - // Don't use `into_iter` here, it may cause memory leak. - let mut result = groups.iter_mut(); - let cardinality = self.chunk_size; - loop { - let mut group_builders: Vec<_> = self - .group_key_types - .iter() - .map(|datatype| datatype.create_array_builder(cardinality)) - .collect(); - - let mut agg_builders: Vec<_> = self - .aggs - .iter() - .map(|agg| agg.return_type().create_array_builder(cardinality)) - .collect(); - - let mut has_next = false; - let mut array_len = 0; - for (key, states) in result.by_ref().take(cardinality) { - self.shutdown_rx.check()?; - has_next = true; - array_len += 1; - key.deserialize_to_builders(&mut group_builders[..], &self.group_key_types)?; - for ((agg, state), builder) in (self.aggs.iter()) - .zip_eq_fast(states) - .zip_eq_fast(&mut agg_builders) - { - let result = agg.get_result(state).await?; - builder.append(result); + if need_to_spill { + // A spilling version of aggregation based on the RFC: Spill Hash Aggregation https://github.com/risingwavelabs/rfcs/pull/89 + // When HashAggExecutor told memory is insufficient, AggSpillManager will start to partition the hash table and spill to disk. + // After spilling the hash table, AggSpillManager will consume all chunks from the input executor, + // partition and spill to disk with the same hash function as the hash table spilling. + // Finally, we would get e.g. 20 partitions. Each partition should contain a portion of the original hash table and input data. + // A sub HashAggExecutor would be used to consume each partition one by one. + // If memory is still not enough in the sub HashAggExecutor, it will spill its hash table and input recursively. + info!( + "batch hash agg executor {} starts to spill out", + &self.identity + ); + let mut agg_spill_manager = AggSpillManager::new( + self.spill_backend.clone().unwrap(), + &self.identity, + DEFAULT_SPILL_PARTITION_NUM, + self.group_key_types.clone(), + self.aggs.iter().map(|agg| agg.return_type()).collect(), + child_schema.data_types(), + self.chunk_size, + self.spill_metrics.clone(), + )?; + agg_spill_manager.init_writers().await?; + + let mut memory_usage_diff = 0; + // Spill agg states. + for (key, states) in groups { + let key_row = key.deserialize(&self.group_key_types)?; + let mut agg_datums = vec![]; + for (agg, state) in self.aggs.iter().zip_eq_fast(states) { + let encode_state = agg.encode_state(&state)?; + memory_usage_diff -= state.estimated_size() as i64; + agg_datums.push(encode_state); } + let agg_state_row = OwnedRow::from_iter(agg_datums.into_iter()); + let hash_code = agg_spill_manager.spill_build_hasher.hash_one(key); + agg_spill_manager + .write_agg_state_row(key_row.chain(agg_state_row), hash_code) + .await?; + } + + // Release memory occupied by agg hash map. + self.mem_context.add(memory_usage_diff); + + // Spill input chunks. + #[for_await] + for chunk in input_stream { + let chunk: DataChunk = chunk?; + let hash_codes = chunk.get_hash_values( + self.group_key_columns.as_slice(), + agg_spill_manager.spill_build_hasher, + ); + agg_spill_manager + .write_input_chunk( + chunk, + hash_codes + .into_iter() + .map(|hash_code| hash_code.value()) + .collect(), + ) + .await?; } - if !has_next { - break; // exit loop + + agg_spill_manager.close_writers().await?; + + // Process each partition one by one. + for i in 0..agg_spill_manager.partition_num { + let partition_size = agg_spill_manager.estimate_partition_size(i).await?; + + let agg_state_stream = agg_spill_manager.read_agg_state_partition(i).await?; + let input_stream = agg_spill_manager.read_input_partition(i).await?; + + let sub_hash_agg_executor: HashAggExecutor = HashAggExecutor::new_inner( + self.aggs.clone(), + self.group_key_columns.clone(), + self.group_key_types.clone(), + self.schema.clone(), + Box::new(WrapStreamExecutor::new(child_schema.clone(), input_stream)), + Some(Box::new(WrapStreamExecutor::new( + self.schema.clone(), + agg_state_stream, + ))), + format!("{}-sub{}", self.identity.clone(), i), + self.chunk_size, + self.mem_context.clone(), + self.spill_backend.clone(), + self.spill_metrics.clone(), + Some(partition_size), + self.shutdown_rx.clone(), + ); + + debug!( + "create sub_hash_agg {} for hash_agg {} to spill", + sub_hash_agg_executor.identity, self.identity + ); + + let sub_hash_agg_stream = Box::new(sub_hash_agg_executor).execute(); + + #[for_await] + for chunk in sub_hash_agg_stream { + let chunk = chunk?; + yield chunk; + } + + // Clear files of the current partition. + agg_spill_manager.clear_partition(i).await?; } + } else { + // Don't use `into_iter` here, it may cause memory leak. + let mut result = groups.iter_mut(); + let cardinality = self.chunk_size; + loop { + let mut group_builders: Vec<_> = self + .group_key_types + .iter() + .map(|datatype| datatype.create_array_builder(cardinality)) + .collect(); - let columns = group_builders - .into_iter() - .chain(agg_builders) - .map(|b| b.finish().into()) - .collect::>(); + let mut agg_builders: Vec<_> = self + .aggs + .iter() + .map(|agg| agg.return_type().create_array_builder(cardinality)) + .collect(); - let output = DataChunk::new(columns, array_len); - yield output; + let mut has_next = false; + let mut array_len = 0; + for (key, states) in result.by_ref().take(cardinality) { + self.shutdown_rx.check()?; + has_next = true; + array_len += 1; + key.deserialize_to_builders(&mut group_builders[..], &self.group_key_types)?; + for ((agg, state), builder) in (self.aggs.iter()) + .zip_eq_fast(states) + .zip_eq_fast(&mut agg_builders) + { + let result = agg.get_result(state).await?; + builder.append(result); + } + } + if !has_next { + break; // exit loop + } + + let columns = group_builders + .into_iter() + .chain(agg_builders) + .map(|b| b.finish().into()) + .collect::>(); + + let output = DataChunk::new(columns, array_len); + yield output; + } } } } @@ -305,12 +748,11 @@ mod tests { use std::alloc::{AllocError, Allocator, Global, Layout}; use std::ptr::NonNull; use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; use futures_async_stream::for_await; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::metrics::LabelGuardedIntGauge; use risingwave_common::test_prelude::DataChunkTestExt; + use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_pb::data::data_type::TypeName; use risingwave_pb::data::PbDataType; use risingwave_pb::expr::agg_call::Type; @@ -318,12 +760,13 @@ mod tests { use super::*; use crate::executor::test_utils::{diff_executor_output, MockExecutor}; + use crate::executor::SortExecutor; const CHUNK_SIZE: usize = 1024; #[tokio::test] async fn execute_int32_grouped() { - let parent_mem = MemoryContext::root(LabelGuardedIntGauge::<4>::test_int_gauge()); + let parent_mem = MemoryContext::root(LabelGuardedIntGauge::<4>::test_int_gauge(), u64::MAX); { let src_exec = Box::new(MockExecutor::with_chunk( DataChunk::from_pretty( @@ -361,6 +804,7 @@ mod tests { order_by: vec![], filter: None, direct_args: vec![], + udf: None, }; let agg_prost = HashAggNode { @@ -379,6 +823,8 @@ mod tests { "HashAggExecutor".to_string(), CHUNK_SIZE, mem_context.clone(), + None, + BatchSpillMetrics::for_test(), ShutdownToken::empty(), ) .unwrap(); @@ -436,6 +882,7 @@ mod tests { order_by: vec![], filter: None, direct_args: vec![], + udf: None, }; let agg_prost = HashAggNode { @@ -450,6 +897,8 @@ mod tests { "HashAggExecutor".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), ShutdownToken::empty(), ) .unwrap(); @@ -477,6 +926,7 @@ mod tests { #[derive(Clone)] struct MyAlloc { + #[expect(dead_code)] inner: Arc, } @@ -549,6 +999,7 @@ mod tests { order_by: vec![], filter: None, direct_args: vec![], + udf: None, }; let agg_prost = HashAggNode { @@ -564,6 +1015,8 @@ mod tests { "HashAggExecutor".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), shutdown_rx, ) .unwrap(); @@ -576,4 +1029,109 @@ mod tests { break; } } + + fn create_order_by_executor(child: BoxedExecutor) -> BoxedExecutor { + let column_orders = child + .schema() + .fields + .iter() + .enumerate() + .map(|(i, _)| ColumnOrder { + column_index: i, + order_type: OrderType::ascending(), + }) + .collect_vec(); + + Box::new(SortExecutor::new( + child, + Arc::new(column_orders), + "SortExecutor".into(), + CHUNK_SIZE, + MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), + )) + } + + #[tokio::test] + async fn test_spill_hash_agg() { + let src_exec = Box::new(MockExecutor::with_chunk( + DataChunk::from_pretty( + "i i i + 0 1 1 + 1 1 1 + 0 0 1 + 1 1 2 + 1 0 1 + 0 0 2 + 1 1 3 + 0 1 2", + ), + Schema::new(vec![ + Field::unnamed(DataType::Int32), + Field::unnamed(DataType::Int32), + Field::unnamed(DataType::Int64), + ]), + )); + + let agg_call = AggCall { + r#type: Type::Sum as i32, + args: vec![InputRef { + index: 2, + r#type: Some(PbDataType { + type_name: TypeName::Int32 as i32, + ..Default::default() + }), + }], + return_type: Some(PbDataType { + type_name: TypeName::Int64 as i32, + ..Default::default() + }), + distinct: false, + order_by: vec![], + filter: None, + direct_args: vec![], + udf: None, + }; + + let agg_prost = HashAggNode { + group_key: vec![0, 1], + agg_calls: vec![agg_call], + }; + + let mem_context = + MemoryContext::new_with_mem_limit(None, LabelGuardedIntGauge::<4>::test_int_gauge(), 0); + let actual_exec = HashAggExecutorBuilder::deserialize( + &agg_prost, + src_exec, + TaskId::default(), + "HashAggExecutor".to_string(), + CHUNK_SIZE, + mem_context.clone(), + Some(SpillBackend::Memory), + BatchSpillMetrics::for_test(), + ShutdownToken::empty(), + ) + .unwrap(); + + let actual_exec = create_order_by_executor(actual_exec); + + let expect_exec = Box::new(MockExecutor::with_chunk( + DataChunk::from_pretty( + "i i I + 1 0 1 + 0 0 3 + 0 1 3 + 1 1 6", + ), + Schema::new(vec![ + Field::unnamed(DataType::Int32), + Field::unnamed(DataType::Int32), + Field::unnamed(DataType::Int64), + ]), + )); + + let expect_exec = create_order_by_executor(expect_exec); + diff_executor_output(actual_exec, expect_exec).await; + } } diff --git a/src/batch/src/executor/hop_window.rs b/src/batch/src/executor/hop_window.rs index d287efc4667d7..5345aeaa90552 100644 --- a/src/batch/src/executor/hop_window.rs +++ b/src/batch/src/executor/hop_window.rs @@ -32,7 +32,6 @@ pub struct HopWindowExecutor { child: BoxedExecutor, identity: String, schema: Schema, - time_col_idx: usize, window_slide: Interval, window_size: Interval, window_start_exprs: Vec, @@ -52,7 +51,6 @@ impl BoxedExecutorBuilder for HopWindowExecutor { source.plan_node().get_node_body().unwrap(), NodeBody::HopWindow )?; - let time_col = hop_window_node.get_time_col() as usize; let window_slide = hop_window_node.get_window_slide()?.into(); let window_size = hop_window_node.get_window_size()?.into(); let output_indices = hop_window_node @@ -74,6 +72,7 @@ impl BoxedExecutorBuilder for HopWindowExecutor { .try_collect()?; assert_eq!(window_start_exprs.len(), window_end_exprs.len()); + let time_col = hop_window_node.get_time_col() as usize; let time_col_data_type = child.schema().fields()[time_col].data_type(); let output_type = DataType::window_of(&time_col_data_type).unwrap(); let original_schema: Schema = child @@ -93,7 +92,6 @@ impl BoxedExecutorBuilder for HopWindowExecutor { Ok(Box::new(HopWindowExecutor::new( child, output_indices_schema, - time_col, window_slide, window_size, source.plan_node().get_identity().clone(), @@ -109,7 +107,6 @@ impl HopWindowExecutor { fn new( child: BoxedExecutor, schema: Schema, - time_col_idx: usize, window_slide: Interval, window_size: Interval, identity: String, @@ -121,7 +118,6 @@ impl HopWindowExecutor { child, identity, schema, - time_col_idx, window_slide, window_size, window_start_exprs, @@ -209,10 +205,8 @@ impl HopWindowExecutor { #[cfg(test)] mod tests { use futures::stream::StreamExt; - use risingwave_common::array::{DataChunk, DataChunkTestExt}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::DataChunkTestExt; use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::DataType; use risingwave_expr::expr::test_utils::make_hop_window_expression; use super::*; @@ -256,7 +250,6 @@ mod tests { Box::new(HopWindowExecutor::new( Box::new(mock_executor), schema, - 2, window_slide, window_size, "test".to_string(), diff --git a/src/batch/src/executor/iceberg_scan.rs b/src/batch/src/executor/iceberg_scan.rs index e20b1aaedb8fe..7bf3835944b3a 100644 --- a/src/batch/src/executor/iceberg_scan.rs +++ b/src/batch/src/executor/iceberg_scan.rs @@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::future; use std::hash::{DefaultHasher, Hash, Hasher}; -use std::sync::Arc; use anyhow::anyhow; -use arrow_array::RecordBatch; use futures_async_stream::try_stream; use futures_util::stream::StreamExt; -use icelake::io::{FileScan, TableScan}; +use risingwave_common::array::arrow::IcebergArrowConvert; use risingwave_common::catalog::Schema; use risingwave_connector::sink::iceberg::IcebergConfig; @@ -44,7 +43,7 @@ use crate::executor::{DataChunk, Executor}; /// database_name: Some("demo_db".into()), /// table_name: "demo_table".into(), /// catalog_type: Some("storage".into()), -/// path: "s3a://hummock001/".into(), +/// path: "s3://hummock001/".into(), /// endpoint: Some("http://127.0.0.1:9301".into()), /// access_key: "hummockadmin".into(), /// secret_key: "hummockadmin".into(), @@ -116,49 +115,60 @@ impl IcebergScanExecutor { #[try_stream(ok = DataChunk, error = BatchError)] async fn do_execute(self: Box) { - let table = self.iceberg_config.load_table().await?; - - let table_scan: TableScan = table - .new_scan_builder() - .with_snapshot_id( - self.snapshot_id - .unwrap_or_else(|| table.current_table_metadata().current_snapshot_id.unwrap()), - ) - .with_batch_size(self.batch_size) - .with_column_names(self.schema.names()) + let table = self.iceberg_config.load_table_v2().await?; + + let snapshot_id = if let Some(snapshot_id) = self.snapshot_id { + snapshot_id + } else { + table + .metadata() + .current_snapshot() + .ok_or_else(|| { + BatchError::Internal(anyhow!("No snapshot found for iceberg table")) + })? + .snapshot_id() + }; + let scan = table + .scan() + .snapshot_id(snapshot_id) + .with_batch_size(Some(self.batch_size)) + .select(self.schema.names()) .build() .map_err(|e| BatchError::Internal(anyhow!(e)))?; - let file_scan_stream: icelake::io::FileScanStream = - table_scan.scan(&table).await.map_err(BatchError::Iceberg)?; - #[for_await] - for file_scan in file_scan_stream { - let file_scan: FileScan = file_scan.map_err(BatchError::Iceberg)?; - if !self.file_selector.select(file_scan.path()) { - continue; - } - let record_batch_stream = file_scan.scan().await.map_err(BatchError::Iceberg)?; - - #[for_await] - for record_batch in record_batch_stream { - let record_batch: RecordBatch = record_batch.map_err(BatchError::Iceberg)?; - let chunk = Self::record_batch_to_chunk(record_batch)?; - debug_assert_eq!(chunk.data_types(), self.schema.data_types()); - yield chunk; - } - } - } + let file_selector = self.file_selector.clone(); + let file_scan_stream = scan + .plan_files() + .await + .map_err(BatchError::Iceberg)? + .filter(move |task| { + let res = task + .as_ref() + .map(|task| file_selector.select(task.data_file_path())) + .unwrap_or(true); + future::ready(res) + }); + + let reader = table + .reader_builder() + .with_batch_size(self.batch_size) + .build(); - fn record_batch_to_chunk(record_batch: RecordBatch) -> Result { - let mut columns = Vec::with_capacity(record_batch.num_columns()); - for array in record_batch.columns() { - let column = Arc::new(array.try_into()?); - columns.push(column); + let record_batch_stream = reader + .read(Box::pin(file_scan_stream)) + .map_err(BatchError::Iceberg)?; + + #[for_await] + for record_batch in record_batch_stream { + let record_batch = record_batch.map_err(BatchError::Iceberg)?; + let chunk = IcebergArrowConvert.chunk_from_record_batch(&record_batch)?; + debug_assert_eq!(chunk.data_types(), self.schema.data_types()); + yield chunk; } - Ok(DataChunk::new(columns, record_batch.num_rows())) } } +#[derive(Clone)] pub enum FileSelector { // File paths to be scanned by this executor are specified. FileList(Vec), diff --git a/src/batch/src/executor/insert.rs b/src/batch/src/executor/insert.rs index aa4063be84bb5..5674e17557e6b 100644 --- a/src/batch/src/executor/insert.rs +++ b/src/batch/src/executor/insert.rs @@ -43,6 +43,7 @@ pub struct InsertExecutor { table_version_id: TableVersionId, dml_manager: DmlManagerRef, child: BoxedExecutor, + #[expect(dead_code)] chunk_size: usize, schema: Schema, identity: String, @@ -264,18 +265,16 @@ impl BoxedExecutorBuilder for InsertExecutor { #[cfg(test)] mod tests { use std::ops::Bound; - use std::sync::Arc; use assert_matches::assert_matches; - use foyer::memory::CacheContext; + use foyer::CacheContext; use futures::StreamExt; - use itertools::Itertools; use risingwave_common::array::{Array, ArrayImpl, I32Array, StructArray}; use risingwave_common::catalog::{ schema_test_utils, ColumnDesc, ColumnId, INITIAL_TABLE_VERSION_ID, }; use risingwave_common::transaction::transaction_message::TxnMsg; - use risingwave_common::types::{DataType, StructType}; + use risingwave_common::types::StructType; use risingwave_dml::dml_manager::DmlManager; use risingwave_storage::hummock::CachePolicy; use risingwave_storage::memory::MemoryStateStore; diff --git a/src/batch/src/executor/join/chunked_data.rs b/src/batch/src/executor/join/chunked_data.rs index 066ca88d4bbaa..158f9cba9f289 100644 --- a/src/batch/src/executor/join/chunked_data.rs +++ b/src/batch/src/executor/join/chunked_data.rs @@ -122,17 +122,6 @@ impl ChunkedData { chunk_offsets: &self.chunk_offsets, } } - - pub(super) fn next_row_id(&self, cur: RowId) -> Option { - let current_chunk_row_count = - self.chunk_offsets[cur.chunk_id() + 1] - self.chunk_offsets[cur.chunk_id()]; - let next = cur.next_row(current_chunk_row_count); - if (next.chunk_id() + 1) >= self.chunk_offsets.len() { - None - } else { - Some(next) - } - } } impl Index for ChunkedData { diff --git a/src/batch/src/executor/join/hash_join.rs b/src/batch/src/executor/join/hash_join.rs index c9bc0caad7cef..7880b5f69f9ab 100644 --- a/src/batch/src/executor/join/hash_join.rs +++ b/src/batch/src/executor/join/hash_join.rs @@ -17,10 +17,12 @@ use std::iter::empty; use std::marker::PhantomData; use std::sync::Arc; +use bytes::Bytes; use futures_async_stream::try_stream; use itertools::Itertools; +use prost::Message; use risingwave_common::array::{Array, DataChunk, RowRef}; -use risingwave_common::buffer::{Bitmap, BitmapBuilder}; +use risingwave_common::bitmap::{Bitmap, BitmapBuilder}; use risingwave_common::catalog::Schema; use risingwave_common::hash::{HashKey, HashKeyDispatcher, PrecomputedBuildHasher}; use risingwave_common::memory::{MemoryContext, MonitoredGlobalAlloc}; @@ -31,13 +33,20 @@ use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common_estimate_size::EstimateSize; use risingwave_expr::expr::{build_from_prost, BoxedExpression, Expression}; use risingwave_pb::batch_plan::plan_node::NodeBody; +use risingwave_pb::data::DataChunk as PbDataChunk; use super::{ChunkedData, JoinType, RowId}; use crate::error::{BatchError, Result}; use crate::executor::{ BoxedDataChunkStream, BoxedExecutor, BoxedExecutorBuilder, Executor, ExecutorBuilder, + WrapStreamExecutor, }; +use crate::monitor::BatchSpillMetrics; use crate::risingwave_common::hash::NullBitmap; +use crate::spill::spill_op::SpillBackend::Disk; +use crate::spill::spill_op::{ + SpillBackend, SpillBuildHasher, SpillOp, DEFAULT_SPILL_PARTITION_NUM, SPILL_AT_LEAST_MEMORY, +}; use crate::task::{BatchTaskContext, ShutdownToken}; /// Hash Join Executor @@ -47,12 +56,13 @@ use crate::task::{BatchTaskContext, ShutdownToken}; /// 2. Iterate over the probe side (i.e. left table) and compute the hash value of each row. /// Then find the matched build side row for each probe side row in the hash map. /// 3. Concatenate the matched pair of probe side row and build side row into a single row and push -/// it into the data chunk builder. +/// it into the data chunk builder. /// 4. Yield chunks from the builder. pub struct HashJoinExecutor { /// Join type e.g. inner, left outer, ... join_type: JoinType, /// Output schema without applying `output_indices` + #[expect(dead_code)] original_schema: Schema, /// Output schema after applying `output_indices` schema: Schema, @@ -67,13 +77,18 @@ pub struct HashJoinExecutor { /// Column indices of right keys in equi join build_key_idxs: Vec, /// Non-equi join condition (optional) - cond: Option, + cond: Option>, /// Whether or not to enable 'IS NOT DISTINCT FROM' semantics for a specific probe/build key /// column null_matched: Vec, identity: String, chunk_size: usize, + spill_backend: Option, + spill_metrics: Arc, + /// The upper bound of memory usage for this executor. + memory_upper_bound: Option, + shutdown_rx: ShutdownToken, mem_ctx: MemoryContext, @@ -220,22 +235,268 @@ struct RightNonEquiJoinState { build_row_matched: ChunkedData, } +pub struct JoinSpillManager { + op: SpillOp, + partition_num: usize, + probe_side_writers: Vec, + build_side_writers: Vec, + probe_side_chunk_builders: Vec, + build_side_chunk_builders: Vec, + spill_build_hasher: SpillBuildHasher, + probe_side_data_types: Vec, + build_side_data_types: Vec, + spill_chunk_size: usize, + spill_metrics: Arc, +} + +/// `JoinSpillManager` is used to manage how to write spill data file and read them back. +/// The spill data first need to be partitioned. Each partition contains 2 files: `join_probe_side_file` and `join_build_side_file`. +/// The spill file consume a data chunk and serialize the chunk into a protobuf bytes. +/// Finally, spill file content will look like the below. +/// The file write pattern is append-only and the read pattern is sequential scan. +/// This can maximize the disk IO performance. +/// +/// ```text +/// [proto_len] +/// [proto_bytes] +/// ... +/// [proto_len] +/// [proto_bytes] +/// ``` +impl JoinSpillManager { + pub fn new( + spill_backend: SpillBackend, + join_identity: &String, + partition_num: usize, + probe_side_data_types: Vec, + build_side_data_types: Vec, + spill_chunk_size: usize, + spill_metrics: Arc, + ) -> Result { + let suffix_uuid = uuid::Uuid::new_v4(); + let dir = format!("/{}-{}/", join_identity, suffix_uuid); + let op = SpillOp::create(dir, spill_backend)?; + let probe_side_writers = Vec::with_capacity(partition_num); + let build_side_writers = Vec::with_capacity(partition_num); + let probe_side_chunk_builders = Vec::with_capacity(partition_num); + let build_side_chunk_builders = Vec::with_capacity(partition_num); + let spill_build_hasher = SpillBuildHasher(suffix_uuid.as_u64_pair().1); + Ok(Self { + op, + partition_num, + probe_side_writers, + build_side_writers, + probe_side_chunk_builders, + build_side_chunk_builders, + spill_build_hasher, + probe_side_data_types, + build_side_data_types, + spill_chunk_size, + spill_metrics, + }) + } + + pub async fn init_writers(&mut self) -> Result<()> { + for i in 0..self.partition_num { + let join_probe_side_partition_file_name = format!("join-probe-side-p{}", i); + let w = self + .op + .writer_with(&join_probe_side_partition_file_name) + .await?; + self.probe_side_writers.push(w); + + let join_build_side_partition_file_name = format!("join-build-side-p{}", i); + let w = self + .op + .writer_with(&join_build_side_partition_file_name) + .await?; + self.build_side_writers.push(w); + self.probe_side_chunk_builders.push(DataChunkBuilder::new( + self.probe_side_data_types.clone(), + self.spill_chunk_size, + )); + self.build_side_chunk_builders.push(DataChunkBuilder::new( + self.build_side_data_types.clone(), + self.spill_chunk_size, + )); + } + Ok(()) + } + + pub async fn write_probe_side_chunk( + &mut self, + chunk: DataChunk, + hash_codes: Vec, + ) -> Result<()> { + let (columns, vis) = chunk.into_parts_v2(); + for partition in 0..self.partition_num { + let new_vis = vis.clone() + & Bitmap::from_iter( + hash_codes + .iter() + .map(|hash_code| (*hash_code as usize % self.partition_num) == partition), + ); + let new_chunk = DataChunk::from_parts(columns.clone(), new_vis); + for output_chunk in self.probe_side_chunk_builders[partition].append_chunk(new_chunk) { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.probe_side_writers[partition].write(len_bytes).await?; + self.probe_side_writers[partition].write(buf).await?; + } + } + Ok(()) + } + + pub async fn write_build_side_chunk( + &mut self, + chunk: DataChunk, + hash_codes: Vec, + ) -> Result<()> { + let (columns, vis) = chunk.into_parts_v2(); + for partition in 0..self.partition_num { + let new_vis = vis.clone() + & Bitmap::from_iter( + hash_codes + .iter() + .map(|hash_code| (*hash_code as usize % self.partition_num) == partition), + ); + let new_chunk = DataChunk::from_parts(columns.clone(), new_vis); + for output_chunk in self.build_side_chunk_builders[partition].append_chunk(new_chunk) { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.build_side_writers[partition].write(len_bytes).await?; + self.build_side_writers[partition].write(buf).await?; + } + } + Ok(()) + } + + pub async fn close_writers(&mut self) -> Result<()> { + for partition in 0..self.partition_num { + if let Some(output_chunk) = self.probe_side_chunk_builders[partition].consume_all() { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.probe_side_writers[partition].write(len_bytes).await?; + self.probe_side_writers[partition].write(buf).await?; + } + + if let Some(output_chunk) = self.build_side_chunk_builders[partition].consume_all() { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.build_side_writers[partition].write(len_bytes).await?; + self.build_side_writers[partition].write(buf).await?; + } + } + + for mut w in self.probe_side_writers.drain(..) { + w.close().await?; + } + for mut w in self.build_side_writers.drain(..) { + w.close().await?; + } + Ok(()) + } + + async fn read_probe_side_partition( + &mut self, + partition: usize, + ) -> Result { + let join_probe_side_partition_file_name = format!("join-probe-side-p{}", partition); + let r = self + .op + .reader_with(&join_probe_side_partition_file_name) + .await?; + Ok(SpillOp::read_stream(r, self.spill_metrics.clone())) + } + + async fn read_build_side_partition( + &mut self, + partition: usize, + ) -> Result { + let join_build_side_partition_file_name = format!("join-build-side-p{}", partition); + let r = self + .op + .reader_with(&join_build_side_partition_file_name) + .await?; + Ok(SpillOp::read_stream(r, self.spill_metrics.clone())) + } + + pub async fn estimate_partition_size(&self, partition: usize) -> Result { + let join_probe_side_partition_file_name = format!("join-probe-side-p{}", partition); + let probe_size = self + .op + .stat(&join_probe_side_partition_file_name) + .await? + .content_length(); + let join_build_side_partition_file_name = format!("join-build-side-p{}", partition); + let build_size = self + .op + .stat(&join_build_side_partition_file_name) + .await? + .content_length(); + Ok(probe_size + build_size) + } + + async fn clear_partition(&mut self, partition: usize) -> Result<()> { + let join_probe_side_partition_file_name = format!("join-probe-side-p{}", partition); + self.op.delete(&join_probe_side_partition_file_name).await?; + let join_build_side_partition_file_name = format!("join-build-side-p{}", partition); + self.op.delete(&join_build_side_partition_file_name).await?; + Ok(()) + } +} + impl HashJoinExecutor { #[try_stream(boxed, ok = DataChunk, error = BatchError)] async fn do_execute(self: Box) { + let mut need_to_spill = false; + // If the memory upper bound is less than 1MB, we don't need to check memory usage. + let check_memory = match self.memory_upper_bound { + Some(upper_bound) => upper_bound > SPILL_AT_LEAST_MEMORY, + None => true, + }; + + let probe_schema = self.probe_side_source.schema().clone(); + let build_schema = self.build_side_source.schema().clone(); let probe_data_types = self.probe_side_source.schema().data_types(); let build_data_types = self.build_side_source.schema().data_types(); let full_data_types = [probe_data_types.clone(), build_data_types.clone()].concat(); let mut build_side = Vec::new_in(self.mem_ctx.global_allocator()); let mut build_row_count = 0; + let mut build_side_stream = self.build_side_source.execute(); #[for_await] - for build_chunk in self.build_side_source.execute() { + for build_chunk in &mut build_side_stream { let build_chunk = build_chunk?; if build_chunk.cardinality() > 0 { build_row_count += build_chunk.cardinality(); - self.mem_ctx.add(build_chunk.estimated_heap_size() as i64); + let chunk_estimated_heap_size = build_chunk.estimated_heap_size(); + // push build_chunk to build_side before checking memory limit, otherwise we will lose that chunk when spilling. build_side.push(build_chunk); + if !self.mem_ctx.add(chunk_estimated_heap_size as i64) && check_memory { + if self.spill_backend.is_some() { + need_to_spill = true; + break; + } else { + Err(BatchError::OutOfMemory(self.mem_ctx.mem_limit()))?; + } + } } } let mut hash_map = JoinHashMap::with_capacity_and_hasher_in( @@ -246,92 +507,242 @@ impl HashJoinExecutor { let mut next_build_row_with_same_key = ChunkedData::with_chunk_sizes(build_side.iter().map(|c| c.capacity()))?; - let null_matched = K::Bitmap::from_bool_vec(self.null_matched); + let null_matched = K::Bitmap::from_bool_vec(self.null_matched.clone()); - // Build hash map - for (build_chunk_id, build_chunk) in build_side.iter().enumerate() { - let build_keys = K::build_many(&self.build_key_idxs, build_chunk); + let mut mem_added_by_hash_table = 0; + if !need_to_spill { + // Build hash map + for (build_chunk_id, build_chunk) in build_side.iter().enumerate() { + let build_keys = K::build_many(&self.build_key_idxs, build_chunk); - for (build_row_id, (build_key, visible)) in build_keys - .into_iter() - .zip_eq_fast(build_chunk.visibility().iter()) - .enumerate() - { - self.shutdown_rx.check()?; - if !visible { - continue; - } - // Only insert key to hash map if it is consistent with the null safe restriction. - if build_key.null_bitmap().is_subset(&null_matched) { - let row_id = RowId::new(build_chunk_id, build_row_id); - self.mem_ctx.add(build_key.estimated_heap_size() as i64); - next_build_row_with_same_key[row_id] = hash_map.insert(build_key, row_id); + for (build_row_id, (build_key, visible)) in build_keys + .into_iter() + .zip_eq_fast(build_chunk.visibility().iter()) + .enumerate() + { + self.shutdown_rx.check()?; + if !visible { + continue; + } + // Only insert key to hash map if it is consistent with the null safe restriction. + if build_key.null_bitmap().is_subset(&null_matched) { + let row_id = RowId::new(build_chunk_id, build_row_id); + let build_key_size = build_key.estimated_heap_size() as i64; + mem_added_by_hash_table += build_key_size; + if !self.mem_ctx.add(build_key_size) && check_memory { + if self.spill_backend.is_some() { + need_to_spill = true; + break; + } else { + Err(BatchError::OutOfMemory(self.mem_ctx.mem_limit()))?; + } + } + next_build_row_with_same_key[row_id] = hash_map.insert(build_key, row_id); + } } } } - let params = EquiJoinParams::new( - self.probe_side_source, - probe_data_types, - self.probe_key_idxs, - build_side, - build_data_types, - full_data_types, - hash_map, - next_build_row_with_same_key, - self.chunk_size, - self.shutdown_rx.clone(), - ); + if need_to_spill { + // A spilling version of hash join based on the RFC: Spill Hash Join https://github.com/risingwavelabs/rfcs/pull/91 + // When HashJoinExecutor told memory is insufficient, JoinSpillManager will start to partition the hash table and spill to disk. + // After spilling the hash table, JoinSpillManager will consume all chunks from its build side input executor and probe side input executor. + // Finally, we would get e.g. 20 partitions. Each partition should contain a portion of the original build side input and probr side input data. + // A sub HashJoinExecutor would be used to consume each partition one by one. + // If memory is still not enough in the sub HashJoinExecutor, it will spill its inputs recursively. + info!( + "batch hash join executor {} starts to spill out", + &self.identity + ); + let mut join_spill_manager = JoinSpillManager::new( + self.spill_backend.clone().unwrap(), + &self.identity, + DEFAULT_SPILL_PARTITION_NUM, + probe_data_types.clone(), + build_data_types.clone(), + self.chunk_size, + self.spill_metrics.clone(), + )?; + join_spill_manager.init_writers().await?; + + // Release memory occupied by the hash map + self.mem_ctx.add(-mem_added_by_hash_table); + drop(hash_map); + drop(next_build_row_with_same_key); + + // Spill buffered build side chunks + for chunk in build_side { + // Release the memory occupied by the buffered chunks + self.mem_ctx.add(-(chunk.estimated_heap_size() as i64)); + let hash_codes = chunk.get_hash_values( + self.build_key_idxs.as_slice(), + join_spill_manager.spill_build_hasher, + ); + join_spill_manager + .write_build_side_chunk( + chunk, + hash_codes + .into_iter() + .map(|hash_code| hash_code.value()) + .collect(), + ) + .await?; + } - if let Some(cond) = self.cond.as_ref() { - let stream = match self.join_type { - JoinType::Inner => Self::do_inner_join_with_non_equi_condition(params, cond), - JoinType::LeftOuter => { - Self::do_left_outer_join_with_non_equi_condition(params, cond) - } - JoinType::LeftSemi => Self::do_left_semi_join_with_non_equi_condition(params, cond), - JoinType::LeftAnti => Self::do_left_anti_join_with_non_equi_condition(params, cond), - JoinType::RightOuter => { - Self::do_right_outer_join_with_non_equi_condition(params, cond) - } - JoinType::RightSemi => { - Self::do_right_semi_anti_join_with_non_equi_condition::(params, cond) - } - JoinType::RightAnti => { - Self::do_right_semi_anti_join_with_non_equi_condition::(params, cond) - } - JoinType::FullOuter => { - Self::do_full_outer_join_with_non_equi_condition(params, cond) - } - }; - // For non-equi join, we need an output chunk builder to align the output chunks. - let mut output_chunk_builder = - DataChunkBuilder::new(self.schema.data_types(), self.chunk_size); + // Spill build side chunks #[for_await] - for chunk in stream { - for output_chunk in - output_chunk_builder.append_chunk(chunk?.project(&self.output_indices)) - { - yield output_chunk - } + for chunk in build_side_stream { + let chunk = chunk?; + let hash_codes = chunk.get_hash_values( + self.build_key_idxs.as_slice(), + join_spill_manager.spill_build_hasher, + ); + join_spill_manager + .write_build_side_chunk( + chunk, + hash_codes + .into_iter() + .map(|hash_code| hash_code.value()) + .collect(), + ) + .await?; } - if let Some(output_chunk) = output_chunk_builder.consume_all() { - yield output_chunk + + // Spill probe side chunks + #[for_await] + for chunk in self.probe_side_source.execute() { + let chunk = chunk?; + let hash_codes = chunk.get_hash_values( + self.probe_key_idxs.as_slice(), + join_spill_manager.spill_build_hasher, + ); + join_spill_manager + .write_probe_side_chunk( + chunk, + hash_codes + .into_iter() + .map(|hash_code| hash_code.value()) + .collect(), + ) + .await?; + } + + join_spill_manager.close_writers().await?; + + // Process each partition one by one. + for i in 0..join_spill_manager.partition_num { + let partition_size = join_spill_manager.estimate_partition_size(i).await?; + let probe_side_stream = join_spill_manager.read_probe_side_partition(i).await?; + let build_side_stream = join_spill_manager.read_build_side_partition(i).await?; + + let sub_hash_join_executor: HashJoinExecutor = HashJoinExecutor::new_inner( + self.join_type, + self.output_indices.clone(), + Box::new(WrapStreamExecutor::new( + probe_schema.clone(), + probe_side_stream, + )), + Box::new(WrapStreamExecutor::new( + build_schema.clone(), + build_side_stream, + )), + self.probe_key_idxs.clone(), + self.build_key_idxs.clone(), + self.null_matched.clone(), + self.cond.clone(), + format!("{}-sub{}", self.identity.clone(), i), + self.chunk_size, + self.spill_backend.clone(), + self.spill_metrics.clone(), + Some(partition_size), + self.shutdown_rx.clone(), + self.mem_ctx.clone(), + ); + + debug!( + "create sub_hash_join {} for hash_join {} to spill", + sub_hash_join_executor.identity, self.identity + ); + + let sub_hash_join_executor = Box::new(sub_hash_join_executor).execute(); + + #[for_await] + for chunk in sub_hash_join_executor { + let chunk = chunk?; + yield chunk; + } + + // Clear files of the current partition. + join_spill_manager.clear_partition(i).await?; } } else { - let stream = match self.join_type { - JoinType::Inner => Self::do_inner_join(params), - JoinType::LeftOuter => Self::do_left_outer_join(params), - JoinType::LeftSemi => Self::do_left_semi_anti_join::(params), - JoinType::LeftAnti => Self::do_left_semi_anti_join::(params), - JoinType::RightOuter => Self::do_right_outer_join(params), - JoinType::RightSemi => Self::do_right_semi_anti_join::(params), - JoinType::RightAnti => Self::do_right_semi_anti_join::(params), - JoinType::FullOuter => Self::do_full_outer_join(params), - }; - #[for_await] - for chunk in stream { - yield chunk?.project(&self.output_indices) + let params = EquiJoinParams::new( + self.probe_side_source, + probe_data_types, + self.probe_key_idxs, + build_side, + build_data_types, + full_data_types, + hash_map, + next_build_row_with_same_key, + self.chunk_size, + self.shutdown_rx.clone(), + ); + + if let Some(cond) = self.cond.as_ref() { + let stream = match self.join_type { + JoinType::Inner => Self::do_inner_join_with_non_equi_condition(params, cond), + JoinType::LeftOuter => { + Self::do_left_outer_join_with_non_equi_condition(params, cond) + } + JoinType::LeftSemi => { + Self::do_left_semi_join_with_non_equi_condition(params, cond) + } + JoinType::LeftAnti => { + Self::do_left_anti_join_with_non_equi_condition(params, cond) + } + JoinType::RightOuter => { + Self::do_right_outer_join_with_non_equi_condition(params, cond) + } + JoinType::RightSemi => { + Self::do_right_semi_anti_join_with_non_equi_condition::(params, cond) + } + JoinType::RightAnti => { + Self::do_right_semi_anti_join_with_non_equi_condition::(params, cond) + } + JoinType::FullOuter => { + Self::do_full_outer_join_with_non_equi_condition(params, cond) + } + }; + // For non-equi join, we need an output chunk builder to align the output chunks. + let mut output_chunk_builder = + DataChunkBuilder::new(self.schema.data_types(), self.chunk_size); + #[for_await] + for chunk in stream { + for output_chunk in + output_chunk_builder.append_chunk(chunk?.project(&self.output_indices)) + { + yield output_chunk + } + } + if let Some(output_chunk) = output_chunk_builder.consume_all() { + yield output_chunk + } + } else { + let stream = match self.join_type { + JoinType::Inner => Self::do_inner_join(params), + JoinType::LeftOuter => Self::do_left_outer_join(params), + JoinType::LeftSemi => Self::do_left_semi_anti_join::(params), + JoinType::LeftAnti => Self::do_left_semi_anti_join::(params), + JoinType::RightOuter => Self::do_right_outer_join(params), + JoinType::RightSemi => Self::do_right_semi_anti_join::(params), + JoinType::RightAnti => Self::do_right_semi_anti_join::(params), + JoinType::FullOuter => Self::do_full_outer_join(params), + }; + #[for_await] + for chunk in stream { + yield chunk?.project(&self.output_indices) + } } } } @@ -580,7 +991,7 @@ impl HashJoinExecutor { } shutdown_rx.check()?; if !ANTI_JOIN { - if hash_map.get(probe_key).is_some() { + if hash_map.contains_key(probe_key) { if let Some(spilled) = Self::append_one_probe_row( &mut chunk_builder, &probe_chunk, @@ -1284,7 +1695,7 @@ impl HashJoinExecutor { /// | 4 | 3 | 3 | - | /// /// 3. Remove duplicate rows with NULL build side. This is done by setting the visibility bitmap - /// of the chunk. + /// of the chunk. /// /// | offset | v1 | v2 | v3 | /// |---|---|---|---| @@ -1832,6 +2243,12 @@ impl BoxedExecutorBuilder for HashJoinExecutor<()> { identity: identity.clone(), right_key_types, chunk_size: context.context.get_config().developer.chunk_size, + spill_backend: if context.context.get_config().enable_spill { + Some(Disk) + } else { + None + }, + spill_metrics: context.context.spill_metrics(), shutdown_rx: context.shutdown_rx.clone(), mem_ctx: context.context.create_executor_mem_context(&identity), } @@ -1851,6 +2268,8 @@ struct HashJoinExecutorArgs { identity: String, right_key_types: Vec, chunk_size: usize, + spill_backend: Option, + spill_metrics: Arc, shutdown_rx: ShutdownToken, mem_ctx: MemoryContext, } @@ -1867,9 +2286,11 @@ impl HashKeyDispatcher for HashJoinExecutorArgs { self.probe_key_idxs, self.build_key_idxs, self.null_matched, - self.cond, + self.cond.map(Arc::new), self.identity, self.chunk_size, + self.spill_backend, + self.spill_metrics, self.shutdown_rx, self.mem_ctx, )) @@ -1890,9 +2311,48 @@ impl HashJoinExecutor { probe_key_idxs: Vec, build_key_idxs: Vec, null_matched: Vec, - cond: Option, + cond: Option>, identity: String, chunk_size: usize, + spill_backend: Option, + spill_metrics: Arc, + shutdown_rx: ShutdownToken, + mem_ctx: MemoryContext, + ) -> Self { + Self::new_inner( + join_type, + output_indices, + probe_side_source, + build_side_source, + probe_key_idxs, + build_key_idxs, + null_matched, + cond, + identity, + chunk_size, + spill_backend, + spill_metrics, + None, + shutdown_rx, + mem_ctx, + ) + } + + #[allow(clippy::too_many_arguments)] + fn new_inner( + join_type: JoinType, + output_indices: Vec, + probe_side_source: BoxedExecutor, + build_side_source: BoxedExecutor, + probe_key_idxs: Vec, + build_key_idxs: Vec, + null_matched: Vec, + cond: Option>, + identity: String, + chunk_size: usize, + spill_backend: Option, + spill_metrics: Arc, + memory_upper_bound: Option, shutdown_rx: ShutdownToken, mem_ctx: MemoryContext, ) -> Self { @@ -1929,6 +2389,9 @@ impl HashJoinExecutor { identity, chunk_size, shutdown_rx, + spill_backend, + spill_metrics, + memory_upper_bound, mem_ctx, _phantom: PhantomData, } @@ -1939,6 +2402,7 @@ impl HashJoinExecutor { mod tests { use futures::StreamExt; use futures_async_stream::for_await; + use itertools::Itertools; use risingwave_common::array::{ArrayBuilderImpl, DataChunk}; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::hash::Key32; @@ -1947,6 +2411,8 @@ mod tests { use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::DataType; use risingwave_common::util::iter_util::ZipEqDebug; + use risingwave_common::util::memcmp_encoding::encode_chunk; + use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_expr::expr::{build_from_pretty, BoxedExpression}; use super::{ @@ -1955,12 +2421,13 @@ mod tests { use crate::error::Result; use crate::executor::test_utils::MockExecutor; use crate::executor::BoxedExecutor; + use crate::monitor::BatchSpillMetrics; + use crate::spill::spill_op::SpillBackend; use crate::task::ShutdownToken; const CHUNK_SIZE: usize = 1024; struct DataChunkMerger { - data_types: Vec, array_builders: Vec, array_len: usize, } @@ -1973,7 +2440,6 @@ mod tests { .collect(); Ok(Self { - data_types, array_builders, array_len: 0, }) @@ -2000,7 +2466,8 @@ mod tests { } } - fn is_data_chunk_eq(left: &DataChunk, right: &DataChunk) -> bool { + /// Sort each row in the data chunk and compare with the rows in the data chunk. + fn compare_data_chunk_with_rowsort(left: &DataChunk, right: &DataChunk) -> bool { assert!(left.is_compacted()); assert!(right.is_compacted()); @@ -2008,8 +2475,30 @@ mod tests { return false; } - left.rows() - .zip_eq_debug(right.rows()) + // Sort and compare + let column_orders = (0..left.columns().len()) + .map(|i| ColumnOrder::new(i, OrderType::ascending())) + .collect_vec(); + let left_encoded_chunk = encode_chunk(left, &column_orders).unwrap(); + let mut sorted_left = left_encoded_chunk + .into_iter() + .enumerate() + .map(|(row_id, row)| (left.row_at_unchecked_vis(row_id), row)) + .collect_vec(); + sorted_left.sort_unstable_by(|(_, a), (_, b)| a.cmp(b)); + + let right_encoded_chunk = encode_chunk(right, &column_orders).unwrap(); + let mut sorted_right = right_encoded_chunk + .into_iter() + .enumerate() + .map(|(row_id, row)| (right.row_at_unchecked_vis(row_id), row)) + .collect_vec(); + sorted_right.sort_unstable_by(|(_, a), (_, b)| a.cmp(b)); + + sorted_left + .into_iter() + .map(|(row, _)| row) + .zip_eq_debug(sorted_right.into_iter().map(|(row, _)| row)) .all(|(row1, row2)| row1 == row2) } @@ -2116,10 +2605,6 @@ mod tests { Box::new(executor) } - fn full_data_types(&self) -> Vec { - [self.left_types.clone(), self.right_types.clone()].concat() - } - fn output_data_types(&self) -> Vec { let join_type = self.join_type; if join_type.keep_all() { @@ -2146,6 +2631,7 @@ mod tests { right_child: BoxedExecutor, shutdown_rx: ShutdownToken, parent_mem_ctx: Option, + test_spill: bool, ) -> BoxedExecutor { let join_type = self.join_type; @@ -2157,13 +2643,20 @@ mod tests { .collect(); let cond = if has_non_equi_cond { - Some(Self::create_cond()) + Some(Self::create_cond().into()) } else { None }; - let mem_ctx = - MemoryContext::new(parent_mem_ctx, LabelGuardedIntGauge::<4>::test_int_gauge()); + let mem_ctx = if test_spill { + MemoryContext::new_with_mem_limit( + parent_mem_ctx, + LabelGuardedIntGauge::<4>::test_int_gauge(), + 0, + ) + } else { + MemoryContext::new(parent_mem_ctx, LabelGuardedIntGauge::<4>::test_int_gauge()) + }; Box::new(HashJoinExecutor::::new( join_type, output_indices, @@ -2175,12 +2668,32 @@ mod tests { cond, "HashJoinExecutor".to_string(), chunk_size, + if test_spill { + Some(SpillBackend::Memory) + } else { + None + }, + BatchSpillMetrics::for_test(), shutdown_rx, mem_ctx, )) } async fn do_test(&self, expected: DataChunk, has_non_equi_cond: bool, null_safe: bool) { + let left_executor = self.create_left_executor(); + let right_executor = self.create_right_executor(); + self.do_test_with_chunk_size_and_executors( + expected.clone(), + has_non_equi_cond, + null_safe, + self::CHUNK_SIZE, + left_executor, + right_executor, + false, + ) + .await; + + // Test spill let left_executor = self.create_left_executor(); let right_executor = self.create_right_executor(); self.do_test_with_chunk_size_and_executors( @@ -2190,8 +2703,9 @@ mod tests { self::CHUNK_SIZE, left_executor, right_executor, + true, ) - .await + .await; } async fn do_test_with_chunk_size_and_executors( @@ -2202,9 +2716,10 @@ mod tests { chunk_size: usize, left_executor: BoxedExecutor, right_executor: BoxedExecutor, + test_spill: bool, ) { let parent_mem_context = - MemoryContext::root(LabelGuardedIntGauge::<4>::test_int_gauge()); + MemoryContext::root(LabelGuardedIntGauge::<4>::test_int_gauge(), u64::MAX); { let join_executor = self.create_join_executor_with_chunk_size_and_executors( @@ -2215,6 +2730,7 @@ mod tests { right_executor, ShutdownToken::empty(), Some(parent_mem_context.clone()), + test_spill, ); let mut data_chunk_merger = DataChunkMerger::new(self.output_data_types()).unwrap(); @@ -2246,7 +2762,7 @@ mod tests { // TODO: Replace this with unsorted comparison // assert_eq!(expected, result_chunk); - assert!(is_data_chunk_eq(&expected, &result_chunk)); + assert!(compare_data_chunk_with_rowsort(&expected, &result_chunk)); } assert_eq!(0, parent_mem_context.get_bytes_used()); @@ -2265,6 +2781,7 @@ mod tests { right_executor, shutdown_rx, None, + false, ); shutdown_tx.cancel(); #[for_await] @@ -2285,6 +2802,7 @@ mod tests { right_executor, shutdown_rx, None, + false, ); shutdown_tx.abort("test"); #[for_await] @@ -2701,6 +3219,7 @@ mod tests { 3, Box::new(left_executor), Box::new(right_executor), + false, ) .await; } @@ -2813,7 +3332,7 @@ mod tests { has_more_output_rows: true, found_matched: false, }; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_outer_join_non_equi_condition( chunk, cond.as_ref(), @@ -2843,7 +3362,7 @@ mod tests { ); state.first_output_row_id = vec![2, 3]; state.has_more_output_rows = false; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_outer_join_non_equi_condition( chunk, cond.as_ref(), @@ -2873,7 +3392,7 @@ mod tests { ); state.first_output_row_id = vec![2, 3]; state.has_more_output_rows = false; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_outer_join_non_equi_condition( chunk, cond.as_ref(), @@ -2913,7 +3432,7 @@ mod tests { found_matched: false, ..Default::default() }; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_semi_anti_join_non_equi_condition::( chunk, cond.as_ref(), @@ -2940,7 +3459,7 @@ mod tests { 4 1.0 4 2.0", ); state.first_output_row_id = vec![2, 3]; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_semi_anti_join_non_equi_condition::( chunk, cond.as_ref(), @@ -2967,7 +3486,7 @@ mod tests { 6 7.0 6 8.0", ); state.first_output_row_id = vec![2, 3]; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_semi_anti_join_non_equi_condition::( chunk, cond.as_ref(), @@ -3009,7 +3528,7 @@ mod tests { has_more_output_rows: true, found_matched: false, }; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_semi_anti_join_non_equi_condition::( chunk, cond.as_ref(), @@ -3038,7 +3557,7 @@ mod tests { ); state.first_output_row_id = vec![2, 3]; state.has_more_output_rows = false; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_semi_anti_join_non_equi_condition::( chunk, cond.as_ref(), @@ -3067,7 +3586,7 @@ mod tests { ); state.first_output_row_id = vec![2, 3]; state.has_more_output_rows = false; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_left_semi_anti_join_non_equi_condition::( chunk, cond.as_ref(), @@ -3131,7 +3650,7 @@ mod tests { ], build_row_matched, }; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_right_outer_join_non_equi_condition( chunk, cond.as_ref(), @@ -3172,7 +3691,7 @@ mod tests { RowId::new(0, 12), RowId::new(0, 13), ]; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_right_outer_join_non_equi_condition( chunk, cond.as_ref(), @@ -3321,7 +3840,7 @@ mod tests { ], build_row_matched: ChunkedData::with_chunk_sizes([14].into_iter()).unwrap(), }; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_full_outer_join_non_equi_condition( chunk, cond.as_ref(), @@ -3369,7 +3888,7 @@ mod tests { RowId::new(0, 12), RowId::new(0, 13), ]; - assert!(is_data_chunk_eq( + assert!(compare_data_chunk_with_rowsort( &HashJoinExecutor::::process_full_outer_join_non_equi_condition( chunk, cond.as_ref(), diff --git a/src/batch/src/executor/join/local_lookup_join.rs b/src/batch/src/executor/join/local_lookup_join.rs index 17b257106fb5b..dd428e3166901 100644 --- a/src/batch/src/executor/join/local_lookup_join.rs +++ b/src/batch/src/executor/join/local_lookup_join.rs @@ -17,11 +17,11 @@ use std::marker::PhantomData; use anyhow::Context; use itertools::Itertools; -use risingwave_common::buffer::BitmapBuilder; +use risingwave_common::bitmap::BitmapBuilder; use risingwave_common::catalog::{ColumnDesc, Field, Schema}; use risingwave_common::hash::table_distribution::TableDistribution; use risingwave_common::hash::{ - ExpandedParallelUnitMapping, HashKey, HashKeyDispatcher, ParallelUnitId, VirtualNode, + ExpandedWorkerSlotMapping, HashKey, HashKeyDispatcher, VirtualNode, WorkerSlotId, }; use risingwave_common::memory::MemoryContext; use risingwave_common::types::{DataType, Datum}; @@ -29,7 +29,6 @@ use risingwave_common::util::chunk_coalesce::DataChunkBuilder; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common::util::scan_range::ScanRange; use risingwave_common::util::tracing::TracingContext; -use risingwave_common::util::worker_util::get_pu_to_worker_mapping; use risingwave_expr::expr::{build_from_prost, BoxedExpression}; use risingwave_pb::batch_plan::exchange_info::DistributionMode; use risingwave_pb::batch_plan::exchange_source::LocalExecutePlan::Plan; @@ -52,7 +51,7 @@ use crate::task::{BatchTaskContext, ShutdownToken, TaskId}; struct InnerSideExecutorBuilder { table_desc: StorageTableDesc, table_distribution: TableDistribution, - vnode_mapping: ExpandedParallelUnitMapping, + vnode_mapping: ExpandedWorkerSlotMapping, outer_side_key_types: Vec, inner_side_schema: Schema, inner_side_column_ids: Vec, @@ -61,8 +60,9 @@ struct InnerSideExecutorBuilder { context: C, task_id: TaskId, epoch: BatchQueryEpoch, - pu_to_worker_mapping: HashMap, - pu_to_scan_range_mapping: HashMap>, + worker_slot_mapping: HashMap, + worker_slot_to_scan_range_mapping: HashMap>, + #[expect(dead_code)] chunk_size: usize, shutdown_rx: ShutdownToken, next_stage_id: usize, @@ -91,8 +91,8 @@ impl InnerSideExecutorBuilder { /// Creates the `RowSeqScanNode` that will be used for scanning the inner side table /// based on the passed `scan_range` and virtual node. - fn create_row_seq_scan_node(&self, id: &ParallelUnitId) -> Result { - let list = self.pu_to_scan_range_mapping.get(id).unwrap(); + fn create_row_seq_scan_node(&self, id: &WorkerSlotId) -> Result { + let list = self.worker_slot_to_scan_range_mapping.get(id).unwrap(); let mut scan_ranges = vec![]; let mut vnode_bitmap = BitmapBuilder::zeroed(self.vnode_mapping.len()); @@ -114,11 +114,11 @@ impl InnerSideExecutorBuilder { } /// Creates the `PbExchangeSource` using the given `id`. - fn build_prost_exchange_source(&self, id: &ParallelUnitId) -> Result { + fn build_prost_exchange_source(&self, id: &WorkerSlotId) -> Result { let worker = self - .pu_to_worker_mapping + .worker_slot_mapping .get(id) - .context("No worker node found for the given parallel unit id.")?; + .context("No worker node found for the given worker slot id.")?; let local_execute_plan = LocalExecutePlan { plan: Some(PlanFragment { @@ -145,7 +145,7 @@ impl InnerSideExecutorBuilder { // conflict. query_id: self.task_id.query_id.clone(), stage_id: self.task_id.stage_id + 10000 + self.next_stage_id as u32, - task_id: *id, + task_id: (*id).into(), }), output_id: 0, }), @@ -160,7 +160,7 @@ impl InnerSideExecutorBuilder { #[async_trait::async_trait] impl LookupExecutorBuilder for InnerSideExecutorBuilder { fn reset(&mut self) { - self.pu_to_scan_range_mapping = HashMap::new(); + self.worker_slot_to_scan_range_mapping = HashMap::new(); } /// Adds the scan range made from the given `kwy_scalar_impls` into the parallel unit id @@ -191,11 +191,11 @@ impl LookupExecutorBuilder for InnerSideExecutorBuilder } let vnode = self.get_virtual_node(&scan_range)?; - let parallel_unit_id = self.vnode_mapping[vnode.to_index()]; + let worker_slot_id = self.vnode_mapping[vnode.to_index()]; let list = self - .pu_to_scan_range_mapping - .entry(parallel_unit_id) + .worker_slot_to_scan_range_mapping + .entry(worker_slot_id) .or_default(); list.push((scan_range, vnode)); @@ -207,7 +207,7 @@ impl LookupExecutorBuilder for InnerSideExecutorBuilder async fn build_executor(&mut self) -> Result { self.next_stage_id += 1; let mut sources = vec![]; - for id in self.pu_to_scan_range_mapping.keys() { + for id in self.worker_slot_to_scan_range_mapping.keys() { sources.push(self.build_prost_exchange_source(id)?); } @@ -368,11 +368,26 @@ impl BoxedExecutorBuilder for LocalLookupJoinExecutorBuilder { let null_safe = lookup_join_node.get_null_safe().to_vec(); - let vnode_mapping = lookup_join_node.get_inner_side_vnode_mapping().to_vec(); + let vnode_mapping = lookup_join_node + .get_inner_side_vnode_mapping() + .iter() + .copied() + .map(WorkerSlotId::from) + .collect_vec(); + assert!(!vnode_mapping.is_empty()); let chunk_size = source.context.get_config().developer.chunk_size; + let worker_nodes = lookup_join_node.get_worker_nodes(); + let worker_slot_mapping: HashMap = worker_nodes + .iter() + .flat_map(|worker| { + (0..(worker.parallelism())) + .map(|i| (WorkerSlotId::new(worker.id, i), worker.clone())) + }) + .collect(); + let inner_side_builder = InnerSideExecutorBuilder { table_desc: table_desc.clone(), table_distribution: TableDistribution::new_from_storage_table_desc( @@ -388,11 +403,11 @@ impl BoxedExecutorBuilder for LocalLookupJoinExecutorBuilder { context: source.context().clone(), task_id: source.task_id.clone(), epoch: source.epoch(), - pu_to_worker_mapping: get_pu_to_worker_mapping(lookup_join_node.get_worker_nodes()), - pu_to_scan_range_mapping: HashMap::new(), + worker_slot_to_scan_range_mapping: HashMap::new(), chunk_size, shutdown_rx: source.shutdown_rx.clone(), next_stage_id: 0, + worker_slot_mapping, }; let identity = source.plan_node().get_identity().clone(); @@ -472,6 +487,8 @@ impl HashKeyDispatcher for LocalLookupJoinExecutorArgs { #[cfg(test)] mod tests { + use std::sync::Arc; + use risingwave_common::array::{DataChunk, DataChunkTestExt}; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::hash::HashKeyDispatcher; @@ -487,14 +504,11 @@ mod tests { diff_executor_output, FakeInnerSideExecutorBuilder, MockExecutor, }; use crate::executor::{BoxedExecutor, SortExecutor}; + use crate::monitor::BatchSpillMetrics; use crate::task::ShutdownToken; const CHUNK_SIZE: usize = 1024; - pub struct MockGatherExecutor { - chunks: Vec, - } - fn create_outer_side_input() -> BoxedExecutor { let schema = Schema { fields: vec![ @@ -583,10 +597,12 @@ mod tests { Box::new(SortExecutor::new( child, - column_orders, + Arc::new(column_orders), "SortExecutor".into(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )) } diff --git a/src/batch/src/executor/join/lookup_join_base.rs b/src/batch/src/executor/join/lookup_join_base.rs index 72af8ef418449..39a39b9a1424d 100644 --- a/src/batch/src/executor/join/lookup_join_base.rs +++ b/src/batch/src/executor/join/lookup_join_base.rs @@ -121,9 +121,9 @@ impl LookupJoinBase { ] .concat(); - // We need to temporary variable to record hash key heap size, since in each loop we + // We need to temporary variable to record heap size, since in each loop we // will free build side hash map, and the subtraction is not executed automatically. - let mut hash_key_heap_size = 0i64; + let mut tmp_heap_size = 0i64; let mut build_side = Vec::new_in(self.mem_ctx.global_allocator()); let mut build_row_count = 0; @@ -132,7 +132,9 @@ impl LookupJoinBase { let build_chunk = build_chunk?; if build_chunk.cardinality() > 0 { build_row_count += build_chunk.cardinality(); - self.mem_ctx.add(build_chunk.estimated_heap_size() as i64); + let chunk_estimated_heap_size = build_chunk.estimated_heap_size() as i64; + self.mem_ctx.add(chunk_estimated_heap_size); + tmp_heap_size += chunk_estimated_heap_size; build_side.push(build_chunk); } } @@ -160,8 +162,9 @@ impl LookupJoinBase { // restriction. if build_key.null_bitmap().is_subset(&null_matched) { let row_id = RowId::new(build_chunk_id, build_row_id); - self.mem_ctx.add(build_key.estimated_heap_size() as i64); - hash_key_heap_size += build_key.estimated_heap_size() as i64; + let build_key_estimated_heap_size = build_key.estimated_heap_size() as i64; + self.mem_ctx.add(build_key_estimated_heap_size); + tmp_heap_size += build_key_estimated_heap_size; next_build_row_with_same_key[row_id] = hash_map.insert(build_key, row_id); } } @@ -230,7 +233,7 @@ impl LookupJoinBase { } } - self.mem_ctx.add(-hash_key_heap_size); + self.mem_ctx.add(-tmp_heap_size); } } } diff --git a/src/batch/src/executor/join/mod.rs b/src/batch/src/executor/join/mod.rs index 2f05174aca3db..cf2388314d8f6 100644 --- a/src/batch/src/executor/join/mod.rs +++ b/src/batch/src/executor/join/mod.rs @@ -30,7 +30,7 @@ use risingwave_common::array::{DataChunk, RowRef}; use risingwave_common::row::Row; use risingwave_common::types::{DataType, DatumRef}; use risingwave_common::util::iter_util::ZipEqFast; -use risingwave_pb::plan_common::JoinType as JoinTypePb; +use risingwave_pb::plan_common::JoinType as PbJoinType; use crate::error::Result; @@ -52,6 +52,25 @@ pub enum JoinType { } impl JoinType { + pub fn from_prost(prost: PbJoinType) -> Self { + match prost { + PbJoinType::Inner => JoinType::Inner, + PbJoinType::LeftOuter => JoinType::LeftOuter, + PbJoinType::LeftSemi => JoinType::LeftSemi, + PbJoinType::LeftAnti => JoinType::LeftAnti, + PbJoinType::RightOuter => JoinType::RightOuter, + PbJoinType::RightSemi => JoinType::RightSemi, + PbJoinType::RightAnti => JoinType::RightAnti, + PbJoinType::FullOuter => JoinType::FullOuter, + PbJoinType::Unspecified => unreachable!(), + } + } +} + +#[cfg(test)] +impl JoinType { + #![allow(dead_code)] + #[inline(always)] pub(super) fn need_join_remaining(self) -> bool { matches!( @@ -60,20 +79,6 @@ impl JoinType { ) } - pub fn from_prost(prost: JoinTypePb) -> Self { - match prost { - JoinTypePb::Inner => JoinType::Inner, - JoinTypePb::LeftOuter => JoinType::LeftOuter, - JoinTypePb::LeftSemi => JoinType::LeftSemi, - JoinTypePb::LeftAnti => JoinType::LeftAnti, - JoinTypePb::RightOuter => JoinType::RightOuter, - JoinTypePb::RightSemi => JoinType::RightSemi, - JoinTypePb::RightAnti => JoinType::RightAnti, - JoinTypePb::FullOuter => JoinType::FullOuter, - JoinTypePb::Unspecified => unreachable!(), - } - } - fn need_build(self) -> bool { match self { JoinType::RightSemi => true, @@ -173,7 +178,7 @@ fn convert_row_to_chunk( mod tests { use risingwave_common::array::{Array, ArrayBuilder, DataChunk, PrimitiveArrayBuilder}; - use risingwave_common::buffer::Bitmap; + use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::row::Row; use risingwave_common::types::{DataType, ScalarRefImpl}; diff --git a/src/batch/src/executor/join/nested_loop_join.rs b/src/batch/src/executor/join/nested_loop_join.rs index 43b6448ad39f2..ae3e5b8fb4b35 100644 --- a/src/batch/src/executor/join/nested_loop_join.rs +++ b/src/batch/src/executor/join/nested_loop_join.rs @@ -15,7 +15,7 @@ use futures_async_stream::try_stream; use risingwave_common::array::data_chunk_iter::RowRef; use risingwave_common::array::{Array, DataChunk}; -use risingwave_common::buffer::BitmapBuilder; +use risingwave_common::bitmap::BitmapBuilder; use risingwave_common::catalog::Schema; use risingwave_common::memory::MemoryContext; use risingwave_common::row::{repeat_n, RowExt}; @@ -98,7 +98,9 @@ impl NestedLoopJoinExecutor { for chunk in self.left_child.execute() { let c = chunk?; trace!("Estimated chunk size is {:?}", c.estimated_heap_size()); - self.mem_context.add(c.estimated_heap_size() as i64); + if !self.mem_context.add(c.estimated_heap_size() as i64) { + Err(BatchError::OutOfMemory(self.mem_context.mem_limit()))?; + } ret.push(c); } ret @@ -527,8 +529,6 @@ mod tests { const CHUNK_SIZE: usize = 1024; struct TestFixture { - left_types: Vec, - right_types: Vec, join_type: JoinType, } @@ -549,11 +549,7 @@ mod tests { /// ``` impl TestFixture { fn with_join_type(join_type: JoinType) -> Self { - Self { - left_types: vec![DataType::Int32, DataType::Float32], - right_types: vec![DataType::Int32, DataType::Float64], - join_type, - } + Self { join_type } } fn create_left_executor(&self) -> BoxedExecutor { diff --git a/src/batch/src/executor/limit.rs b/src/batch/src/executor/limit.rs index eb57a78e1ab12..35e7a096714e2 100644 --- a/src/batch/src/executor/limit.rs +++ b/src/batch/src/executor/limit.rs @@ -17,7 +17,7 @@ use std::cmp::min; use futures_async_stream::try_stream; use itertools::Itertools; use risingwave_common::array::DataChunk; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::Schema; use risingwave_pb::batch_plan::plan_node::NodeBody; @@ -150,8 +150,8 @@ mod tests { use std::vec; use futures_async_stream::for_await; - use risingwave_common::array::{Array, ArrayRef, BoolArray, DataChunk, PrimitiveArray}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::{Array, ArrayRef, BoolArray, PrimitiveArray}; + use risingwave_common::catalog::Field; use risingwave_common::types::DataType; use risingwave_common::util::iter_util::ZipEqDebug; diff --git a/src/batch/src/executor/log_row_seq_scan.rs b/src/batch/src/executor/log_row_seq_scan.rs new file mode 100644 index 0000000000000..9ca18a9654f7e --- /dev/null +++ b/src/batch/src/executor/log_row_seq_scan.rs @@ -0,0 +1,250 @@ +// Copyright 2024 RisingWave Labs +// +// 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::ops::{Bound, Deref}; +use std::sync::Arc; + +use futures::prelude::stream::StreamExt; +use futures_async_stream::try_stream; +use futures_util::pin_mut; +use itertools::Itertools; +use prometheus::Histogram; +use risingwave_common::array::DataChunk; +use risingwave_common::bitmap::Bitmap; +use risingwave_common::catalog::{ColumnId, Field, Schema}; +use risingwave_common::row::{OwnedRow, Row}; +use risingwave_common::types::ScalarImpl; +use risingwave_pb::batch_plan::plan_node::NodeBody; +use risingwave_pb::common::BatchQueryEpoch; +use risingwave_pb::plan_common::StorageTableDesc; +use risingwave_storage::table::batch_table::storage_table::StorageTable; +use risingwave_storage::table::{collect_data_chunk, KeyedRow, TableDistribution}; +use risingwave_storage::{dispatch_state_store, StateStore}; + +use super::{BoxedDataChunkStream, BoxedExecutor, BoxedExecutorBuilder, Executor, ExecutorBuilder}; +use crate::error::{BatchError, Result}; +use crate::monitor::BatchMetricsWithTaskLabels; +use crate::task::BatchTaskContext; + +pub struct LogRowSeqScanExecutor { + chunk_size: usize, + identity: String, + // It is table schema + op column + schema: Schema, + + /// Batch metrics. + /// None: Local mode don't record mertics. + metrics: Option, + + table: StorageTable, + old_epoch: BatchQueryEpoch, + new_epoch: BatchQueryEpoch, +} + +impl LogRowSeqScanExecutor { + pub fn new( + table: StorageTable, + old_epoch: BatchQueryEpoch, + new_epoch: BatchQueryEpoch, + chunk_size: usize, + identity: String, + metrics: Option, + ) -> Self { + let mut schema = table.schema().clone(); + schema.fields.push(Field::with_name( + risingwave_common::types::DataType::Int16, + "op", + )); + Self { + chunk_size, + identity, + schema, + metrics, + table, + old_epoch, + new_epoch, + } + } +} + +pub struct LogStoreRowSeqScanExecutorBuilder {} + +#[async_trait::async_trait] +impl BoxedExecutorBuilder for LogStoreRowSeqScanExecutorBuilder { + async fn new_boxed_executor( + source: &ExecutorBuilder<'_, C>, + inputs: Vec, + ) -> Result { + ensure!( + inputs.is_empty(), + "LogStore row sequential scan should not have input executor!" + ); + let log_store_seq_scan_node = try_match_expand!( + source.plan_node().get_node_body().unwrap(), + NodeBody::LogRowSeqScan + )?; + + let table_desc: &StorageTableDesc = log_store_seq_scan_node.get_table_desc()?; + let column_ids = log_store_seq_scan_node + .column_ids + .iter() + .copied() + .map(ColumnId::from) + .collect(); + + let vnodes = match &log_store_seq_scan_node.vnode_bitmap { + Some(vnodes) => Some(Bitmap::from(vnodes).into()), + // This is possible for dml. vnode_bitmap is not filled by scheduler. + // Or it's single distribution, e.g., distinct agg. We scan in a single executor. + None => Some(TableDistribution::all_vnodes()), + }; + + let chunk_size = source.context.get_config().developer.chunk_size as u32; + let metrics = source.context().batch_metrics(); + + dispatch_state_store!(source.context().state_store(), state_store, { + let table = StorageTable::new_partial(state_store, column_ids, vnodes, table_desc); + Ok(Box::new(LogRowSeqScanExecutor::new( + table, + log_store_seq_scan_node.old_epoch.clone().unwrap(), + log_store_seq_scan_node.new_epoch.clone().unwrap(), + chunk_size as usize, + source.plan_node().get_identity().clone(), + metrics, + ))) + }) + } +} +impl Executor for LogRowSeqScanExecutor { + fn schema(&self) -> &Schema { + &self.schema + } + + fn identity(&self) -> &str { + &self.identity + } + + fn execute(self: Box) -> BoxedDataChunkStream { + self.do_execute().boxed() + } +} + +impl LogRowSeqScanExecutor { + #[try_stream(ok = DataChunk, error = BatchError)] + async fn do_execute(self: Box) { + let Self { + chunk_size, + identity, + metrics, + table, + old_epoch, + new_epoch, + schema, + } = *self; + let table = std::sync::Arc::new(table); + + // Create collector. + let histogram = metrics.as_ref().map(|metrics| { + metrics + .executor_metrics() + .row_seq_scan_next_duration + .with_guarded_label_values(&metrics.executor_labels(&identity)) + }); + // Range Scan + // WARN: DO NOT use `select` to execute range scans concurrently + // it can consume too much memory if there're too many ranges. + let stream = Self::execute_range( + table.clone(), + old_epoch.clone(), + new_epoch.clone(), + chunk_size, + histogram.clone(), + Arc::new(schema.clone()), + ); + #[for_await] + for chunk in stream { + let chunk = chunk?; + yield chunk; + } + } + + #[try_stream(ok = DataChunk, error = BatchError)] + async fn execute_range( + table: Arc>, + old_epoch: BatchQueryEpoch, + new_epoch: BatchQueryEpoch, + chunk_size: usize, + histogram: Option>, + schema: Arc, + ) { + let pk_prefix = OwnedRow::default(); + + let order_type = table.pk_serializer().get_order_types()[pk_prefix.len()]; + // Range Scan. + let iter = table + .batch_iter_log_with_pk_bounds( + old_epoch.into(), + new_epoch.into(), + &pk_prefix, + ( + if order_type.nulls_are_first() { + // `NULL`s are at the start bound side, we should exclude them to meet SQL semantics. + Bound::Excluded(OwnedRow::new(vec![None])) + } else { + // Both start and end are unbounded, so we need to select all rows. + Bound::Unbounded + }, + if order_type.nulls_are_last() { + // `NULL`s are at the end bound side, we should exclude them to meet SQL semantics. + Bound::Excluded(OwnedRow::new(vec![None])) + } else { + // Both start and end are unbounded, so we need to select all rows. + Bound::Unbounded + }, + ), + ) + .await? + .map(|r| match r { + Ok((op, value)) => { + let (k, row) = value.into_owned_row_key(); + // Todo! To avoid create a full row. + let full_row = row + .into_iter() + .chain(vec![Some(ScalarImpl::Int16(op.to_i16()))]) + .collect_vec(); + let row = OwnedRow::new(full_row); + Ok(KeyedRow::<_>::new(k, row)) + } + Err(e) => Err(e), + }); + + pin_mut!(iter); + loop { + let timer = histogram.as_ref().map(|histogram| histogram.start_timer()); + + let chunk = collect_data_chunk(&mut iter, &schema, Some(chunk_size)) + .await + .map_err(BatchError::from)?; + if let Some(timer) = timer { + timer.observe_duration() + } + + if let Some(chunk) = chunk { + yield chunk + } else { + break; + } + } + } +} diff --git a/src/batch/src/executor/merge_sort.rs b/src/batch/src/executor/merge_sort.rs new file mode 100644 index 0000000000000..1f5c8f3e5fc2c --- /dev/null +++ b/src/batch/src/executor/merge_sort.rs @@ -0,0 +1,195 @@ +// Copyright 2024 RisingWave Labs +// +// 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::mem; +use std::sync::Arc; + +use futures_async_stream::try_stream; +use futures_util::StreamExt; +use itertools::Itertools; +use risingwave_common::array::DataChunk; +use risingwave_common::catalog::Schema; +use risingwave_common::memory::{MemMonitoredHeap, MemoryContext, MonitoredGlobalAlloc}; +use risingwave_common::types::ToOwnedDatum; +use risingwave_common::util::sort_util::{ColumnOrder, HeapElem}; +use risingwave_common_estimate_size::EstimateSize; + +use super::{BoxedDataChunkStream, BoxedExecutor, Executor}; +use crate::error::{BatchError, Result}; + +pub struct MergeSortExecutor { + inputs: Vec, + column_orders: Arc>, + identity: String, + schema: Schema, + chunk_size: usize, + mem_context: MemoryContext, + min_heap: MemMonitoredHeap, + current_chunks: Vec, MonitoredGlobalAlloc>, +} + +impl Executor for MergeSortExecutor { + fn schema(&self) -> &Schema { + &self.schema + } + + fn identity(&self) -> &str { + &self.identity + } + + fn execute(self: Box) -> BoxedDataChunkStream { + self.do_execute() + } +} + +impl MergeSortExecutor { + #[try_stream(boxed, ok = DataChunk, error = BatchError)] + async fn do_execute(mut self: Box) { + let mut inputs = vec![]; + mem::swap(&mut inputs, &mut self.inputs); + let mut input_streams = inputs + .into_iter() + .map(|input| input.execute()) + .collect_vec(); + for (input_idx, input_stream) in input_streams.iter_mut().enumerate() { + match input_stream.next().await { + Some(chunk) => { + let chunk = chunk?; + self.current_chunks.push(Some(chunk)); + if let Some(chunk) = &self.current_chunks[input_idx] { + // We assume that we would always get a non-empty chunk from the upstream of + // exchange, therefore we are sure that there is at least + // one visible row. + let next_row_idx = chunk.next_visible_row_idx(0); + self.push_row_into_heap(input_idx, next_row_idx.unwrap()); + } + } + None => { + self.current_chunks.push(None); + } + } + } + + while !self.min_heap.is_empty() { + // It is possible that we cannot produce this much as + // we may run out of input data chunks from sources. + let mut want_to_produce = self.chunk_size; + + let mut builders: Vec<_> = self + .schema + .fields + .iter() + .map(|field| field.data_type.create_array_builder(self.chunk_size)) + .collect(); + let mut array_len = 0; + while want_to_produce > 0 && !self.min_heap.is_empty() { + let top_elem = self.min_heap.pop().unwrap(); + let child_idx = top_elem.chunk_idx(); + let cur_chunk = top_elem.chunk(); + let row_idx = top_elem.elem_idx(); + for (idx, builder) in builders.iter_mut().enumerate() { + let chunk_arr = cur_chunk.column_at(idx); + let chunk_arr = chunk_arr.as_ref(); + let datum = chunk_arr.value_at(row_idx).to_owned_datum(); + builder.append(&datum); + } + want_to_produce -= 1; + array_len += 1; + // check whether we have another row from the same chunk being popped + let possible_next_row_idx = cur_chunk.next_visible_row_idx(row_idx + 1); + match possible_next_row_idx { + Some(next_row_idx) => { + self.push_row_into_heap(child_idx, next_row_idx); + } + None => { + self.get_input_chunk(&mut input_streams, child_idx).await?; + if let Some(chunk) = &self.current_chunks[child_idx] { + let next_row_idx = chunk.next_visible_row_idx(0); + self.push_row_into_heap(child_idx, next_row_idx.unwrap()); + } + } + } + } + + let columns = builders + .into_iter() + .map(|builder| builder.finish().into()) + .collect::>(); + let chunk = DataChunk::new(columns, array_len); + yield chunk + } + } + + async fn get_input_chunk( + &mut self, + input_streams: &mut Vec, + input_idx: usize, + ) -> Result<()> { + assert!(input_idx < input_streams.len()); + let res = input_streams[input_idx].next().await; + let old = match res { + Some(chunk) => { + let chunk = chunk?; + assert_ne!(chunk.cardinality(), 0); + let new_chunk_size = chunk.estimated_heap_size() as i64; + let old = std::mem::replace(&mut self.current_chunks[input_idx], Some(chunk)); + self.mem_context.add(new_chunk_size); + old + } + None => std::mem::take(&mut self.current_chunks[input_idx]), + }; + + if let Some(chunk) = old { + // Reduce the heap size of retired chunk + self.mem_context.add(-(chunk.estimated_heap_size() as i64)); + } + + Ok(()) + } + + fn push_row_into_heap(&mut self, input_idx: usize, row_idx: usize) { + assert!(input_idx < self.current_chunks.len()); + let chunk_ref = self.current_chunks[input_idx].as_ref().unwrap(); + self.min_heap.push(HeapElem::new( + self.column_orders.clone(), + chunk_ref.clone(), + input_idx, + row_idx, + None, + )); + } +} + +impl MergeSortExecutor { + pub fn new( + inputs: Vec, + column_orders: Arc>, + schema: Schema, + identity: String, + chunk_size: usize, + mem_context: MemoryContext, + ) -> Self { + let inputs_num = inputs.len(); + Self { + inputs, + column_orders, + identity, + schema, + chunk_size, + min_heap: MemMonitoredHeap::with_capacity(inputs_num, mem_context.clone()), + current_chunks: Vec::with_capacity_in(inputs_num, mem_context.global_allocator()), + mem_context, + } + } +} diff --git a/src/batch/src/executor/merge_sort_exchange.rs b/src/batch/src/executor/merge_sort_exchange.rs index 1562f2a8f4557..3b5647729db25 100644 --- a/src/batch/src/executor/merge_sort_exchange.rs +++ b/src/batch/src/executor/merge_sort_exchange.rs @@ -17,18 +17,15 @@ use std::sync::Arc; use futures_async_stream::try_stream; use risingwave_common::array::DataChunk; use risingwave_common::catalog::{Field, Schema}; -use risingwave_common::memory::{MemMonitoredHeap, MemoryContext, MonitoredGlobalAlloc}; -use risingwave_common::types::ToOwnedDatum; -use risingwave_common::util::sort_util::{ColumnOrder, HeapElem}; -use risingwave_common_estimate_size::EstimateSize; +use risingwave_common::memory::MemoryContext; +use risingwave_common::util::sort_util::ColumnOrder; use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::PbExchangeSource; use crate::error::{BatchError, Result}; -use crate::exchange_source::ExchangeSourceImpl; use crate::executor::{ BoxedDataChunkStream, BoxedExecutor, BoxedExecutorBuilder, CreateSource, DefaultCreateSource, - Executor, ExecutorBuilder, + Executor, ExecutorBuilder, MergeSortExecutor, WrapStreamExecutor, }; use crate::task::{BatchTaskContext, TaskId}; @@ -38,12 +35,8 @@ pub type MergeSortExchangeExecutor = MergeSortExchangeExecutorImpl { context: C, - /// keeps one data chunk of each source if any - source_inputs: Vec, MonitoredGlobalAlloc>, column_orders: Arc>, - min_heap: MemMonitoredHeap, proto_sources: Vec, - sources: Vec, // impl /// Mock-able `CreateSource`. source_creators: Vec, schema: Schema, @@ -52,7 +45,6 @@ pub struct MergeSortExchangeExecutorImpl { /// The maximum size of the chunk produced by executor at a time. chunk_size: usize, mem_ctx: MemoryContext, - alloc: MonitoredGlobalAlloc, } impl MergeSortExchangeExecutorImpl { @@ -68,69 +60,18 @@ impl MergeSortExchangeEx chunk_size: usize, ) -> Self { let mem_ctx = context.create_executor_mem_context(&identity); - let alloc = MonitoredGlobalAlloc::with_memory_context(mem_ctx.clone()); - - let source_inputs = { - let mut v = Vec::with_capacity_in(proto_sources.len(), alloc.clone()); - (0..proto_sources.len()).for_each(|_| v.push(None)); - v - }; - - let num_sources = proto_sources.len(); Self { context, - source_inputs, column_orders, - min_heap: MemMonitoredHeap::with_capacity(num_sources, mem_ctx.clone()), proto_sources, - sources: Vec::with_capacity(num_sources), source_creators, schema, task_id, identity, chunk_size, mem_ctx, - alloc, - } - } - - /// We assume that the source would always send `Some(chunk)` with cardinality > 0 - /// or `None`, but never `Some(chunk)` with cardinality == 0. - async fn get_source_chunk(&mut self, source_idx: usize) -> Result<()> { - assert!(source_idx < self.source_inputs.len()); - let res = self.sources[source_idx].take_data().await?; - let old = match res { - Some(chunk) => { - assert_ne!(chunk.cardinality(), 0); - let new_chunk_size = chunk.estimated_heap_size() as i64; - let old = std::mem::replace(&mut self.source_inputs[source_idx], Some(chunk)); - self.mem_ctx.add(new_chunk_size); - old - } - None => std::mem::take(&mut self.source_inputs[source_idx]), - }; - - if let Some(chunk) = old { - // Reduce the heap size of retired chunk - self.mem_ctx.add(-(chunk.estimated_heap_size() as i64)); } - - Ok(()) - } - - // Check whether there is indeed a chunk and there is a visible row sitting at `row_idx` - // in the chunk before calling this function. - fn push_row_into_heap(&mut self, source_idx: usize, row_idx: usize) { - assert!(source_idx < self.source_inputs.len()); - let chunk_ref = self.source_inputs[source_idx].as_ref().unwrap(); - self.min_heap.push(HeapElem::new( - self.column_orders.clone(), - chunk_ref.clone(), - source_idx, - row_idx, - None, - )); } } @@ -154,71 +95,31 @@ impl Executor /// `self.chunk_size` as the executor runs out of input from `sources`. impl MergeSortExchangeExecutorImpl { #[try_stream(boxed, ok = DataChunk, error = BatchError)] - async fn do_execute(mut self: Box) { + async fn do_execute(self: Box) { + let mut sources: Vec = vec![]; for source_idx in 0..self.proto_sources.len() { let new_source = self.source_creators[source_idx] .create_source(self.context.clone(), &self.proto_sources[source_idx]) .await?; - self.sources.push(new_source); - self.get_source_chunk(source_idx).await?; - if let Some(chunk) = &self.source_inputs[source_idx] { - // We assume that we would always get a non-empty chunk from the upstream of - // exchange, therefore we are sure that there is at least - // one visible row. - let next_row_idx = chunk.next_visible_row_idx(0); - self.push_row_into_heap(source_idx, next_row_idx.unwrap()); - } - } - // If there is no rows in the heap, - // we run out of input data chunks and emit `Done`. - while !self.min_heap.is_empty() { - // It is possible that we cannot produce this much as - // we may run out of input data chunks from sources. - let mut want_to_produce = self.chunk_size; + sources.push(Box::new(WrapStreamExecutor::new( + self.schema.clone(), + new_source.take_data_stream(), + ))); + } - let mut builders: Vec<_> = self - .schema() - .fields - .iter() - .map(|field| field.data_type.create_array_builder(self.chunk_size)) - .collect(); - let mut array_len = 0; - while want_to_produce > 0 && !self.min_heap.is_empty() { - let top_elem = self.min_heap.pop().unwrap(); - let child_idx = top_elem.chunk_idx(); - let cur_chunk = top_elem.chunk(); - let row_idx = top_elem.elem_idx(); - for (idx, builder) in builders.iter_mut().enumerate() { - let chunk_arr = cur_chunk.column_at(idx); - let chunk_arr = chunk_arr.as_ref(); - let datum = chunk_arr.value_at(row_idx).to_owned_datum(); - builder.append(&datum); - } - want_to_produce -= 1; - array_len += 1; - // check whether we have another row from the same chunk being popped - let possible_next_row_idx = cur_chunk.next_visible_row_idx(row_idx + 1); - match possible_next_row_idx { - Some(next_row_idx) => { - self.push_row_into_heap(child_idx, next_row_idx); - } - None => { - self.get_source_chunk(child_idx).await?; - if let Some(chunk) = &self.source_inputs[child_idx] { - let next_row_idx = chunk.next_visible_row_idx(0); - self.push_row_into_heap(child_idx, next_row_idx.unwrap()); - } - } - } - } + let merge_sort_executor = Box::new(MergeSortExecutor::new( + sources, + self.column_orders.clone(), + self.schema, + format!("MergeSortExecutor{}", &self.task_id.task_id), + self.chunk_size, + self.mem_ctx, + )); - let columns = builders - .into_iter() - .map(|builder| builder.finish().into()) - .collect::>(); - let chunk = DataChunk::new(columns, array_len); - yield chunk + #[for_await] + for chunk in merge_sort_executor.execute() { + yield chunk?; } } } @@ -273,10 +174,8 @@ impl BoxedExecutorBuilder for MergeSortExchangeExecutorBuilder { #[cfg(test)] mod tests { - use std::sync::Arc; - use futures::StreamExt; - use risingwave_common::array::{Array, DataChunk}; + use risingwave_common::array::Array; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::DataType; use risingwave_common::util::sort_util::OrderType; diff --git a/src/batch/src/executor/mod.rs b/src/batch/src/executor/mod.rs index ded2af7089826..c19bc06c141b9 100644 --- a/src/batch/src/executor/mod.rs +++ b/src/batch/src/executor/mod.rs @@ -24,8 +24,10 @@ mod iceberg_scan; mod insert; mod join; mod limit; +mod log_row_seq_scan; mod managed; mod max_one_row; +mod merge_sort; mod merge_sort_exchange; mod order_by; mod project; @@ -59,6 +61,7 @@ pub use join::*; pub use limit::*; pub use managed::*; pub use max_one_row::*; +pub use merge_sort::*; pub use merge_sort_exchange::*; pub use order_by::*; pub use project::*; @@ -80,6 +83,7 @@ pub use update::*; pub use utils::*; pub use values::*; +use self::log_row_seq_scan::LogStoreRowSeqScanExecutorBuilder; use self::test_utils::{BlockExecutorBuilder, BusyLoopExecutorBuilder}; use crate::error::Result; use crate::executor::sys_row_seq_scan::SysRowSeqScanExecutorBuilder; @@ -239,6 +243,7 @@ impl<'a, C: BatchTaskContext> ExecutorBuilder<'a, C> { // Follow NodeBody only used for test NodeBody::BlockExecutor => BlockExecutorBuilder, NodeBody::BusyLoopExecutor => BusyLoopExecutorBuilder, + NodeBody::LogRowSeqScan => LogStoreRowSeqScanExecutorBuilder, } .await?; @@ -257,8 +262,8 @@ mod tests { use crate::executor::ExecutorBuilder; use crate::task::{ComputeNodeContext, ShutdownToken, TaskId}; - #[test] - fn test_clone_for_plan() { + #[tokio::test] + async fn test_clone_for_plan() { let plan_node = PlanNode::default(); let task_id = &TaskId { task_id: 1, diff --git a/src/batch/src/executor/order_by.rs b/src/batch/src/executor/order_by.rs index b8be2211c5157..1f7cbaacde6bd 100644 --- a/src/batch/src/executor/order_by.rs +++ b/src/batch/src/executor/order_by.rs @@ -12,17 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + +use bytes::Bytes; use futures_async_stream::try_stream; +use itertools::Itertools; +use prost::Message; use risingwave_common::array::DataChunk; use risingwave_common::catalog::Schema; use risingwave_common::memory::MemoryContext; +use risingwave_common::types::DataType; use risingwave_common::util::chunk_coalesce::DataChunkBuilder; use risingwave_common::util::memcmp_encoding::encode_chunk; use risingwave_common::util::sort_util::ColumnOrder; +use risingwave_common_estimate_size::EstimateSize; use risingwave_pb::batch_plan::plan_node::NodeBody; +use risingwave_pb::data::DataChunk as PbDataChunk; -use super::{BoxedDataChunkStream, BoxedExecutor, BoxedExecutorBuilder, Executor, ExecutorBuilder}; +use super::{ + BoxedDataChunkStream, BoxedExecutor, BoxedExecutorBuilder, Executor, ExecutorBuilder, + WrapStreamExecutor, +}; use crate::error::{BatchError, Result}; +use crate::executor::merge_sort::MergeSortExecutor; +use crate::monitor::BatchSpillMetrics; +use crate::spill::spill_op::SpillBackend::Disk; +use crate::spill::spill_op::{ + SpillBackend, SpillOp, DEFAULT_SPILL_PARTITION_NUM, SPILL_AT_LEAST_MEMORY, +}; use crate::task::BatchTaskContext; /// Sort Executor @@ -34,11 +51,15 @@ use crate::task::BatchTaskContext; /// 4. Build and yield data chunks according to the row order pub struct SortExecutor { child: BoxedExecutor, - column_orders: Vec, + column_orders: Arc>, identity: String, schema: Schema, chunk_size: usize, mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, + /// The upper bound of memory usage for this executor. + memory_upper_bound: Option, } impl Executor for SortExecutor { @@ -70,15 +91,21 @@ impl BoxedExecutorBuilder for SortExecutor { .column_orders .iter() .map(ColumnOrder::from_protobuf) - .collect(); + .collect_vec(); let identity = source.plan_node().get_identity(); Ok(Box::new(SortExecutor::new( child, - column_orders, + Arc::new(column_orders), identity.clone(), source.context.get_config().developer.chunk_size, source.context.create_executor_mem_context(identity), + if source.context.get_config().enable_spill { + Some(Disk) + } else { + None + }, + source.context.spill_metrics(), ))) } } @@ -86,12 +113,31 @@ impl BoxedExecutorBuilder for SortExecutor { impl SortExecutor { #[try_stream(boxed, ok = DataChunk, error = BatchError)] async fn do_execute(self: Box) { + let child_schema = self.child.schema().clone(); + let mut need_to_spill = false; + // If the memory upper bound is less than 1MB, we don't need to check memory usage. + let check_memory = match self.memory_upper_bound { + Some(upper_bound) => upper_bound > SPILL_AT_LEAST_MEMORY, + None => true, + }; + let mut chunk_builder = DataChunkBuilder::new(self.schema.data_types(), self.chunk_size); let mut chunks = Vec::new_in(self.mem_context.global_allocator()); + let mut input_stream = self.child.execute(); #[for_await] - for chunk in self.child.execute() { - chunks.push(chunk?.compact()); + for chunk in &mut input_stream { + let chunk = chunk?.compact(); + let chunk_estimated_heap_size = chunk.estimated_heap_size(); + chunks.push(chunk); + if !self.mem_context.add(chunk_estimated_heap_size as i64) && check_memory { + if self.spill_backend.is_some() { + need_to_spill = true; + break; + } else { + Err(BatchError::OutOfMemory(self.mem_context.mem_limit()))?; + } + } } let mut encoded_rows = @@ -99,24 +145,113 @@ impl SortExecutor { for chunk in &chunks { let encoded_chunk = encode_chunk(chunk, &self.column_orders)?; + let chunk_estimated_heap_size = encoded_chunk + .iter() + .map(|x| x.estimated_heap_size()) + .sum::(); encoded_rows.extend( encoded_chunk .into_iter() .enumerate() .map(|(row_id, row)| (chunk.row_at_unchecked_vis(row_id), row)), ); + if !self.mem_context.add(chunk_estimated_heap_size as i64) && check_memory { + if self.spill_backend.is_some() { + need_to_spill = true; + break; + } else { + Err(BatchError::OutOfMemory(self.mem_context.mem_limit()))?; + } + } } - encoded_rows.sort_unstable_by(|(_, a), (_, b)| a.cmp(b)); + if need_to_spill { + // A spilling version of sort, a.k.a. external sort. + // When SortExecutor told memory is insufficient, SortSpillManager will start to partition the sort buffer and spill to disk. + // After spilling the sort buffer, SortSpillManager will consume all chunks from its input executor. + // Finally, we would get e.g. 20 partitions. Each partition should contain a portion of the original input data. + // A sub SortExecutor would be used to sort each partition respectively and then a MergeSortExecutor would be used to merge all sorted partitions. + // If memory is still not enough in the sub SortExecutor, it will spill its inputs recursively. + info!("batch sort executor {} starts to spill out", &self.identity); + let mut sort_spill_manager = SortSpillManager::new( + self.spill_backend.clone().unwrap(), + &self.identity, + DEFAULT_SPILL_PARTITION_NUM, + child_schema.data_types(), + self.chunk_size, + self.spill_metrics.clone(), + )?; + sort_spill_manager.init_writers().await?; - for (row, _) in encoded_rows { - if let Some(spilled) = chunk_builder.append_one_row(row) { - yield spilled + // Release memory + drop(encoded_rows); + + // Spill buffer + for chunk in chunks { + sort_spill_manager.write_input_chunk(chunk).await?; } - } - if let Some(spilled) = chunk_builder.consume_all() { - yield spilled + // Spill input chunks. + #[for_await] + for chunk in input_stream { + let chunk: DataChunk = chunk?; + sort_spill_manager.write_input_chunk(chunk).await?; + } + + sort_spill_manager.close_writers().await?; + + let partition_num = sort_spill_manager.partition_num; + // Merge sorted-partitions + let mut sorted_inputs: Vec = Vec::with_capacity(partition_num); + for i in 0..partition_num { + let partition_size = sort_spill_manager.estimate_partition_size(i).await?; + + let input_stream = sort_spill_manager.read_input_partition(i).await?; + + let sub_sort_executor: SortExecutor = SortExecutor::new_inner( + Box::new(WrapStreamExecutor::new(child_schema.clone(), input_stream)), + self.column_orders.clone(), + format!("{}-sub{}", self.identity.clone(), i), + self.chunk_size, + self.mem_context.clone(), + self.spill_backend.clone(), + self.spill_metrics.clone(), + Some(partition_size), + ); + + debug!( + "create sub_sort {} for sort {} to spill", + sub_sort_executor.identity, self.identity + ); + + sorted_inputs.push(Box::new(sub_sort_executor)); + } + + let merge_sort = MergeSortExecutor::new( + sorted_inputs, + self.column_orders.clone(), + self.schema.clone(), + format!("{}-merge-sort", self.identity.clone()), + self.chunk_size, + self.mem_context.clone(), + ); + + #[for_await] + for chunk in Box::new(merge_sort).execute() { + yield chunk?; + } + } else { + encoded_rows.sort_unstable_by(|(_, a), (_, b)| a.cmp(b)); + + for (row, _) in encoded_rows { + if let Some(spilled) = chunk_builder.append_one_row(row) { + yield spilled + } + } + + if let Some(spilled) = chunk_builder.consume_all() { + yield spilled + } } } } @@ -124,10 +259,34 @@ impl SortExecutor { impl SortExecutor { pub fn new( child: BoxedExecutor, - column_orders: Vec, + column_orders: Arc>, identity: String, chunk_size: usize, mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, + ) -> Self { + Self::new_inner( + child, + column_orders, + identity, + chunk_size, + mem_context, + spill_backend, + spill_metrics, + None, + ) + } + + fn new_inner( + child: BoxedExecutor, + column_orders: Arc>, + identity: String, + chunk_size: usize, + mem_context: MemoryContext, + spill_backend: Option, + spill_metrics: Arc, + memory_upper_bound: Option, ) -> Self { let schema = child.schema().clone(); Self { @@ -137,19 +296,138 @@ impl SortExecutor { schema, chunk_size, mem_context, + spill_backend, + spill_metrics, + memory_upper_bound, } } } +/// `SortSpillManager` is used to manage how to write spill data file and read them back. +/// The spill data first need to be partitioned in a round-robin way. Each partition contains 1 file: `input_chunks_file` +/// The spill file consume a data chunk and serialize the chunk into a protobuf bytes. +/// Finally, spill file content will look like the below. +/// The file write pattern is append-only and the read pattern is sequential scan. +/// This can maximize the disk IO performance. +/// +/// ```text +/// [proto_len] +/// [proto_bytes] +/// ... +/// [proto_len] +/// [proto_bytes] +/// ``` +struct SortSpillManager { + op: SpillOp, + partition_num: usize, + round_robin_idx: usize, + input_writers: Vec, + input_chunk_builders: Vec, + child_data_types: Vec, + spill_chunk_size: usize, + spill_metrics: Arc, +} + +impl SortSpillManager { + fn new( + spill_backend: SpillBackend, + agg_identity: &String, + partition_num: usize, + child_data_types: Vec, + spill_chunk_size: usize, + spill_metrics: Arc, + ) -> Result { + let suffix_uuid = uuid::Uuid::new_v4(); + let dir = format!("/{}-{}/", agg_identity, suffix_uuid); + let op = SpillOp::create(dir, spill_backend)?; + let input_writers = Vec::with_capacity(partition_num); + let input_chunk_builders = Vec::with_capacity(partition_num); + Ok(Self { + op, + partition_num, + input_writers, + input_chunk_builders, + round_robin_idx: 0, + child_data_types, + spill_chunk_size, + spill_metrics, + }) + } + + async fn init_writers(&mut self) -> Result<()> { + for i in 0..self.partition_num { + let partition_file_name = format!("input-chunks-p{}", i); + let w = self.op.writer_with(&partition_file_name).await?; + self.input_writers.push(w); + self.input_chunk_builders.push(DataChunkBuilder::new( + self.child_data_types.clone(), + self.spill_chunk_size, + )); + } + Ok(()) + } + + async fn write_input_chunk(&mut self, chunk: DataChunk) -> Result<()> { + for row in chunk.rows() { + let partition = self.round_robin_idx; + if let Some(chunk) = self.input_chunk_builders[partition].append_one_row(row) { + let chunk_pb: PbDataChunk = chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.input_writers[partition].write(len_bytes).await?; + self.input_writers[partition].write(buf).await?; + } + self.round_robin_idx = (self.round_robin_idx + 1) % self.partition_num; + } + Ok(()) + } + + async fn close_writers(&mut self) -> Result<()> { + for partition in 0..self.partition_num { + if let Some(output_chunk) = self.input_chunk_builders[partition].consume_all() { + let chunk_pb: PbDataChunk = output_chunk.to_protobuf(); + let buf = Message::encode_to_vec(&chunk_pb); + let len_bytes = Bytes::copy_from_slice(&(buf.len() as u32).to_le_bytes()); + self.spill_metrics + .batch_spill_write_bytes + .inc_by((buf.len() + len_bytes.len()) as u64); + self.input_writers[partition].write(len_bytes).await?; + self.input_writers[partition].write(buf).await?; + } + } + + for mut w in self.input_writers.drain(..) { + w.close().await?; + } + Ok(()) + } + + async fn read_input_partition(&mut self, partition: usize) -> Result { + let input_partition_file_name = format!("input-chunks-p{}", partition); + let r = self.op.reader_with(&input_partition_file_name).await?; + Ok(SpillOp::read_stream(r, self.spill_metrics.clone())) + } + + async fn estimate_partition_size(&self, partition: usize) -> Result { + let input_partition_file_name = format!("input-chunks-p{}", partition); + let input_size = self + .op + .stat(&input_partition_file_name) + .await? + .content_length(); + Ok(input_size) + } +} + #[cfg(test)] mod tests { use futures::StreamExt; use risingwave_common::array::*; - use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::test_prelude::DataChunkTestExt; - use risingwave_common::types::{ - DataType, Date, Interval, Scalar, StructType, Time, Timestamp, F32, - }; + use risingwave_common::catalog::Field; + use risingwave_common::types::{Date, Interval, Scalar, StructType, Time, Timestamp, F32}; use risingwave_common::util::sort_util::OrderType; use super::*; @@ -185,10 +463,12 @@ mod tests { let order_by_executor = Box::new(SortExecutor::new( Box::new(mock_executor), - column_orders, + Arc::new(column_orders), "SortExecutor2".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )); let fields = &order_by_executor.schema().fields; assert_eq!(fields[0].data_type, DataType::Int32); @@ -235,10 +515,12 @@ mod tests { ]; let order_by_executor = Box::new(SortExecutor::new( Box::new(mock_executor), - column_orders, + Arc::new(column_orders), "SortExecutor2".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )); let fields = &order_by_executor.schema().fields; assert_eq!(fields[0].data_type, DataType::Float32); @@ -285,10 +567,12 @@ mod tests { ]; let order_by_executor = Box::new(SortExecutor::new( Box::new(mock_executor), - column_orders, + Arc::new(column_orders), "SortExecutor2".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )); let fields = &order_by_executor.schema().fields; assert_eq!(fields[0].data_type, DataType::Varchar); @@ -360,10 +644,12 @@ mod tests { ]; let order_by_executor = Box::new(SortExecutor::new( Box::new(mock_executor), - column_orders, + Arc::new(column_orders), "SortExecutor".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )); let mut stream = order_by_executor.execute(); @@ -440,10 +726,12 @@ mod tests { ]; let order_by_executor = Box::new(SortExecutor::new( Box::new(mock_executor), - column_orders, + Arc::new(column_orders), "SortExecutor".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )); let mut stream = order_by_executor.execute(); @@ -546,10 +834,12 @@ mod tests { ]; let order_by_executor = Box::new(SortExecutor::new( Box::new(mock_executor), - column_orders, + column_orders.into(), "SortExecutor".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )); let mut stream = order_by_executor.execute(); @@ -694,14 +984,70 @@ mod tests { ]; let order_by_executor = Box::new(SortExecutor::new( Box::new(mock_executor), - column_orders, + Arc::new(column_orders), "SortExecutor".to_string(), CHUNK_SIZE, MemoryContext::none(), + None, + BatchSpillMetrics::for_test(), )); let mut stream = order_by_executor.execute(); let res = stream.next().await; assert_eq!(res.unwrap().unwrap(), output_chunk) } + + #[tokio::test] + async fn test_spill_out() { + let schema = Schema { + fields: vec![ + Field::unnamed(DataType::Float32), + Field::unnamed(DataType::Float64), + ], + }; + let mut mock_executor = MockExecutor::new(schema); + mock_executor.add(DataChunk::from_pretty( + " f F + -2.2 3.3 + -1.1 2.2 + 1.1 1.1 + 2.2 -1.1 + 3.3 -2.2", + )); + let column_orders = vec![ + ColumnOrder { + column_index: 1, + order_type: OrderType::ascending(), + }, + ColumnOrder { + column_index: 0, + order_type: OrderType::ascending(), + }, + ]; + let order_by_executor = Box::new(SortExecutor::new( + Box::new(mock_executor), + Arc::new(column_orders), + "SortExecutor2".to_string(), + CHUNK_SIZE, + MemoryContext::for_spill_test(), + Some(SpillBackend::Memory), + BatchSpillMetrics::for_test(), + )); + let fields = &order_by_executor.schema().fields; + assert_eq!(fields[0].data_type, DataType::Float32); + assert_eq!(fields[1].data_type, DataType::Float64); + + let mut stream = order_by_executor.execute(); + let res = stream.next().await; + assert!(res.is_some()); + if let Some(res) = res { + let res = res.unwrap(); + let col0 = res.column_at(0); + assert_eq!(col0.as_float32().value_at(0), Some(3.3.into())); + assert_eq!(col0.as_float32().value_at(1), Some(2.2.into())); + assert_eq!(col0.as_float32().value_at(2), Some(1.1.into())); + assert_eq!(col0.as_float32().value_at(3), Some((-1.1).into())); + assert_eq!(col0.as_float32().value_at(4), Some((-2.2).into())); + } + } } diff --git a/src/batch/src/executor/project.rs b/src/batch/src/executor/project.rs index fc5cddaec0787..7fbc5540b975b 100644 --- a/src/batch/src/executor/project.rs +++ b/src/batch/src/executor/project.rs @@ -108,16 +108,14 @@ impl BoxedExecutorBuilder for ProjectExecutor { #[cfg(test)] mod tests { - use futures::stream::StreamExt; use risingwave_common::array::{Array, I32Array}; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::test_prelude::*; use risingwave_common::types::DataType; use risingwave_expr::expr::{InputRefExpression, LiteralExpression}; use super::*; use crate::executor::test_utils::MockExecutor; - use crate::executor::{Executor, ValuesExecutor}; + use crate::executor::ValuesExecutor; use crate::*; const CHUNK_SIZE: usize = 1024; diff --git a/src/batch/src/executor/project_set.rs b/src/batch/src/executor/project_set.rs index b7291609be586..17eaadab76da2 100644 --- a/src/batch/src/executor/project_set.rs +++ b/src/batch/src/executor/project_set.rs @@ -15,13 +15,16 @@ use either::Either; use futures_async_stream::try_stream; use itertools::Itertools; -use risingwave_common::array::DataChunk; +use risingwave_common::array::{ArrayRef, DataChunk}; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::types::{DataType, DatumRef}; use risingwave_common::util::chunk_coalesce::DataChunkBuilder; use risingwave_common::util::iter_util::ZipEqFast; -use risingwave_expr::table_function::ProjectSetSelectItem; +use risingwave_expr::expr::{self, BoxedExpression}; +use risingwave_expr::table_function::{self, BoxedTableFunction, TableFunctionOutputIter}; use risingwave_pb::batch_plan::plan_node::NodeBody; +use risingwave_pb::expr::project_set_select_item::PbSelectItem; +use risingwave_pb::expr::PbProjectSetSelectItem; use crate::error::{BatchError, Result}; use crate::executor::{ @@ -97,7 +100,7 @@ impl ProjectSetExecutor { && i == row_idx { valid = true; - value + value? } else { None } @@ -170,19 +173,68 @@ impl BoxedExecutorBuilder for ProjectSetExecutor { } } +/// Either a scalar expression or a set-returning function. +/// +/// See also [`PbProjectSetSelectItem`] +#[derive(Debug)] +pub enum ProjectSetSelectItem { + Scalar(BoxedExpression), + Set(BoxedTableFunction), +} + +impl From for ProjectSetSelectItem { + fn from(table_function: BoxedTableFunction) -> Self { + ProjectSetSelectItem::Set(table_function) + } +} + +impl From for ProjectSetSelectItem { + fn from(expr: BoxedExpression) -> Self { + ProjectSetSelectItem::Scalar(expr) + } +} + +impl ProjectSetSelectItem { + pub fn from_prost(prost: &PbProjectSetSelectItem, chunk_size: usize) -> Result { + Ok(match prost.select_item.as_ref().unwrap() { + PbSelectItem::Expr(expr) => Self::Scalar(expr::build_from_prost(expr)?), + PbSelectItem::TableFunction(tf) => { + Self::Set(table_function::build_from_prost(tf, chunk_size)?) + } + }) + } + + pub fn return_type(&self) -> DataType { + match self { + ProjectSetSelectItem::Scalar(expr) => expr.return_type(), + ProjectSetSelectItem::Set(tf) => tf.return_type(), + } + } + + pub async fn eval<'a>( + &'a self, + input: &'a DataChunk, + ) -> Result, ArrayRef>> { + match self { + Self::Set(tf) => Ok(Either::Left( + TableFunctionOutputIter::new(tf.eval(input).await).await?, + )), + Self::Scalar(expr) => Ok(Either::Right(expr.eval(input).await?)), + } + } +} + #[cfg(test)] mod tests { use futures::stream::StreamExt; use futures_async_stream::for_await; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::test_prelude::*; - use risingwave_common::types::DataType; use risingwave_expr::expr::{ExpressionBoxExt, InputRefExpression, LiteralExpression}; use risingwave_expr::table_function::repeat; use super::*; use crate::executor::test_utils::MockExecutor; - use crate::executor::{Executor, ValuesExecutor}; + use crate::executor::ValuesExecutor; use crate::*; const CHUNK_SIZE: usize = 1024; diff --git a/src/batch/src/executor/row_seq_scan.rs b/src/batch/src/executor/row_seq_scan.rs index 4c1261363a72b..e9d4ea10b01e8 100644 --- a/src/batch/src/executor/row_seq_scan.rs +++ b/src/batch/src/executor/row_seq_scan.rs @@ -11,7 +11,7 @@ // 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::ops::{Bound, Deref, RangeBounds}; +use std::ops::{Bound, Deref}; use std::sync::Arc; use futures::{pin_mut, StreamExt}; @@ -19,7 +19,7 @@ use futures_async_stream::try_stream; use itertools::Itertools; use prometheus::Histogram; use risingwave_common::array::DataChunk; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::{ColumnId, Schema}; use risingwave_common::row::{OwnedRow, Row}; use risingwave_common::types::{DataType, Datum}; @@ -31,7 +31,7 @@ use risingwave_pb::common::BatchQueryEpoch; use risingwave_pb::plan_common::StorageTableDesc; use risingwave_storage::store::PrefetchOptions; use risingwave_storage::table::batch_table::storage_table::StorageTable; -use risingwave_storage::table::{collect_data_chunk, TableDistribution}; +use risingwave_storage::table::TableDistribution; use risingwave_storage::{dispatch_state_store, StateStore}; use crate::error::{BatchError, Result}; @@ -67,11 +67,6 @@ pub struct ScanRange { } impl ScanRange { - fn is_full_range(bounds: &impl RangeBounds) -> bool { - matches!(bounds.start_bound(), Bound::Unbounded) - && matches!(bounds.end_bound(), Bound::Unbounded) - } - /// Create a scan range from the prost representation. pub fn new( scan_range: PbScanRange, @@ -177,7 +172,6 @@ impl BoxedExecutorBuilder for RowSeqScanExecutorBuilder { .copied() .map(ColumnId::from) .collect(); - let vnodes = match &seq_scan_node.vnode_bitmap { Some(vnodes) => Some(Bitmap::from(vnodes).into()), // This is possible for dml. vnode_bitmap is not filled by scheduler. @@ -393,7 +387,7 @@ impl RowSeqScanExecutor { // Range Scan. assert!(pk_prefix.len() < table.pk_indices().len()); let iter = table - .batch_iter_with_pk_bounds( + .batch_chunk_iter_with_pk_bounds( epoch.into(), &pk_prefix, ( @@ -425,6 +419,7 @@ impl RowSeqScanExecutor { }, ), ordered, + chunk_size, PrefetchOptions::new(limit.is_none(), true), ) .await?; @@ -433,9 +428,7 @@ impl RowSeqScanExecutor { loop { let timer = histogram.as_ref().map(|histogram| histogram.start_timer()); - let chunk = collect_data_chunk(&mut iter, table.schema(), Some(chunk_size)) - .await - .map_err(BatchError::from)?; + let chunk = iter.next().await.transpose().map_err(BatchError::from)?; if let Some(timer) = timer { timer.observe_duration() diff --git a/src/batch/src/executor/sort_agg.rs b/src/batch/src/executor/sort_agg.rs index 2f7cea6059367..1cb5968045fa6 100644 --- a/src/batch/src/executor/sort_agg.rs +++ b/src/batch/src/executor/sort_agg.rs @@ -109,7 +109,11 @@ impl SortAggExecutor { #[try_stream(boxed, ok = DataChunk, error = BatchError)] async fn do_execute(mut self: Box) { let mut left_capacity = self.output_size_limit; - let mut agg_states = self.aggs.iter().map(|agg| agg.create_state()).collect_vec(); + let mut agg_states: Vec<_> = self + .aggs + .iter() + .map(|agg| agg.create_state()) + .try_collect()?; let (mut group_builders, mut agg_builders) = Self::create_builders(&self.group_key, &self.aggs); let mut curr_group = if self.group_key.is_empty() { @@ -225,7 +229,7 @@ impl SortAggExecutor { { let result = agg.get_result(state).await?; builder.append(result); - *state = agg.create_state(); + *state = agg.create_state()?; } Ok(()) } @@ -360,14 +364,12 @@ mod tests { use futures::StreamExt; use futures_async_stream::for_await; use risingwave_common::array::{Array as _, I64Array}; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::DataType; use risingwave_expr::expr::build_from_pretty; use super::*; use crate::executor::test_utils::MockExecutor; - use crate::task::ShutdownToken; #[tokio::test] async fn execute_count_star_int32() -> Result<()> { diff --git a/src/batch/src/executor/source.rs b/src/batch/src/executor/source.rs index cbc69444af7f9..c71f94ae36b09 100644 --- a/src/batch/src/executor/source.rs +++ b/src/batch/src/executor/source.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; use std::sync::Arc; use futures::StreamExt; @@ -49,7 +48,7 @@ pub struct SourceExecutor { schema: Schema, identity: String, - source_ctrl_opts: SourceCtrlOpts, + chunk_size: usize, } #[async_trait::async_trait] @@ -65,8 +64,7 @@ impl BoxedExecutorBuilder for SourceExecutor { )?; // prepare connector source - let source_props: HashMap = - HashMap::from_iter(source_node.with_properties.clone()); + let source_props = source_node.with_properties.clone(); let config = ConnectorProperties::extract(source_props, false).map_err(BatchError::connector)?; @@ -79,11 +77,6 @@ impl BoxedExecutorBuilder for SourceExecutor { .map(|c| SourceColumnDesc::from(&ColumnDesc::from(c.column_desc.as_ref().unwrap()))) .collect(); - let source_ctrl_opts = SourceCtrlOpts { - chunk_size: source.context().get_config().developer.chunk_size, - rate_limit: None, - }; - let column_ids: Vec<_> = source_node .columns .iter() @@ -144,7 +137,7 @@ impl BoxedExecutorBuilder for SourceExecutor { split_list, schema, identity: source.plan_node().get_identity().clone(), - source_ctrl_opts, + chunk_size: source.context().get_config().developer.chunk_size, })) } } @@ -173,12 +166,15 @@ impl SourceExecutor { u32::MAX, "NA".to_owned(), // source name was not passed in batch plan self.metrics, - self.source_ctrl_opts.clone(), + SourceCtrlOpts { + chunk_size: self.chunk_size, + rate_limit: None, + }, ConnectorProperties::default(), )); let stream = self .source - .to_stream(Some(self.split_list), self.column_ids, source_ctx) + .build_stream(Some(self.split_list), self.column_ids, source_ctx) .await?; #[for_await] diff --git a/src/batch/src/executor/table_function.rs b/src/batch/src/executor/table_function.rs index 6c0fc7f302eed..8ced12ebbf2ca 100644 --- a/src/batch/src/executor/table_function.rs +++ b/src/batch/src/executor/table_function.rs @@ -16,7 +16,7 @@ use futures_async_stream::try_stream; use risingwave_common::array::{ArrayImpl, DataChunk}; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::types::DataType; -use risingwave_expr::table_function::{build_from_prost, BoxedTableFunction}; +use risingwave_expr::table_function::{build_from_prost, check_error, BoxedTableFunction}; use risingwave_pb::batch_plan::plan_node::NodeBody; use super::{BoxedExecutor, BoxedExecutorBuilder}; @@ -28,6 +28,7 @@ pub struct TableFunctionExecutor { schema: Schema, identity: String, table_function: BoxedTableFunction, + #[expect(dead_code)] chunk_size: usize, } @@ -53,6 +54,7 @@ impl TableFunctionExecutor { #[for_await] for chunk in self.table_function.eval(&dummy_chunk).await { let chunk = chunk?; + check_error(&chunk)?; // remove the first column and expand the second column if its data type is struct yield match chunk.column_at(1).as_ref() { ArrayImpl::Struct(struct_array) => struct_array.into(), diff --git a/src/batch/src/executor/test_utils.rs b/src/batch/src/executor/test_utils.rs index 41444d1498875..1d949d6e0e06a 100644 --- a/src/batch/src/executor/test_utils.rs +++ b/src/batch/src/executor/test_utils.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg_attr(not(test), allow(dead_code))] + use std::collections::VecDeque; use assert_matches::assert_matches; @@ -73,7 +75,7 @@ pub fn gen_sorted_data( let mut array_builder = DataType::Int64.create_array_builder(batch_size); for _ in 0..batch_size { - array_builder.append(&data_gen.generate_datum(0)); + array_builder.append(data_gen.generate_datum(0)); } let array = array_builder.finish(); @@ -100,7 +102,7 @@ pub fn gen_projected_data( let mut array_builder = DataType::Int64.create_array_builder(batch_size); for j in 0..batch_size { - array_builder.append(&data_gen.generate_datum(((i + 1) * (j + 1)) as u64)); + array_builder.append(data_gen.generate_datum(((i + 1) * (j + 1)) as u64)); } let chunk = DataChunk::new(vec![array_builder.finish().into()], batch_size); diff --git a/src/batch/src/executor/top_n.rs b/src/batch/src/executor/top_n.rs index c02b84ec5b7ca..a4450a76c17a8 100644 --- a/src/batch/src/executor/top_n.rs +++ b/src/batch/src/executor/top_n.rs @@ -14,7 +14,6 @@ use std::cmp::Ordering; use std::sync::Arc; -use std::vec::Vec; use futures_async_stream::try_stream; use risingwave_common::array::DataChunk; @@ -244,10 +243,6 @@ impl HeapElem { } } - pub fn encoded_row(&self) -> &[u8] { - &self.encoded_row - } - pub fn row(&self) -> impl Row + '_ { &self.row } @@ -296,8 +291,8 @@ impl TopNExecutor { mod tests { use futures::stream::StreamExt; use itertools::Itertools; - use risingwave_common::array::{Array, DataChunk}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::Array; + use risingwave_common::catalog::Field; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::DataType; use risingwave_common::util::sort_util::OrderType; diff --git a/src/batch/src/executor/update.rs b/src/batch/src/executor/update.rs index 9e9c2fa8f543d..a753aef840f52 100644 --- a/src/batch/src/executor/update.rs +++ b/src/batch/src/executor/update.rs @@ -259,7 +259,6 @@ mod tests { use std::sync::Arc; use futures::StreamExt; - use risingwave_common::array::Array; use risingwave_common::catalog::{ schema_test_utils, ColumnDesc, ColumnId, INITIAL_TABLE_VERSION_ID, }; diff --git a/src/batch/src/executor/utils.rs b/src/batch/src/executor/utils.rs index 9c6f162f02268..4f724ec5416c8 100644 --- a/src/batch/src/executor/utils.rs +++ b/src/batch/src/executor/utils.rs @@ -99,3 +99,28 @@ impl DummyExecutor { #[try_stream(boxed, ok = DataChunk, error = BatchError)] async fn do_nothing() {} } + +pub struct WrapStreamExecutor { + schema: Schema, + stream: BoxedDataChunkStream, +} + +impl WrapStreamExecutor { + pub fn new(schema: Schema, stream: BoxedDataChunkStream) -> Self { + Self { schema, stream } + } +} + +impl Executor for WrapStreamExecutor { + fn schema(&self) -> &Schema { + &self.schema + } + + fn identity(&self) -> &str { + "WrapStreamExecutor" + } + + fn execute(self: Box) -> BoxedDataChunkStream { + self.stream + } +} diff --git a/src/batch/src/executor/values.rs b/src/batch/src/executor/values.rs index d401cd41dd195..7eb1a6e9893b5 100644 --- a/src/batch/src/executor/values.rs +++ b/src/batch/src/executor/values.rs @@ -37,6 +37,7 @@ pub struct ValuesExecutor { } impl ValuesExecutor { + #[cfg(test)] pub(crate) fn new( rows: Vec>, schema: Schema, diff --git a/src/batch/src/lib.rs b/src/batch/src/lib.rs index d937c64826550..3a29e2a90b27e 100644 --- a/src/batch/src/lib.rs +++ b/src/batch/src/lib.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![expect(dead_code)] #![allow(clippy::derive_partial_eq_without_eq)] #![feature(trait_alias)] #![feature(exact_size_is_empty)] @@ -25,13 +24,11 @@ #![feature(is_sorted)] #![recursion_limit = "256"] #![feature(let_chains)] -#![feature(bound_map)] #![feature(int_roundings)] #![feature(allocator_api)] #![feature(impl_trait_in_assoc_type)] #![feature(assert_matches)] #![feature(lazy_cell)] -#![feature(array_methods)] #![feature(error_generic_member_access)] #![feature(map_try_insert)] @@ -41,6 +38,7 @@ pub mod execution; pub mod executor; pub mod monitor; pub mod rpc; +pub mod spill; pub mod task; pub mod worker_manager; diff --git a/src/batch/src/monitor/stats.rs b/src/batch/src/monitor/stats.rs index 644f471cc3c1e..58c8997c0b193 100644 --- a/src/batch/src/monitor/stats.rs +++ b/src/batch/src/monitor/stats.rs @@ -14,7 +14,8 @@ use std::sync::{Arc, LazyLock}; -use prometheus::{IntGauge, Registry}; +use prometheus::core::{AtomicU64, GenericCounter}; +use prometheus::{register_int_counter_with_registry, IntGauge, Registry}; use risingwave_common::metrics::{ LabelGuardedGauge, LabelGuardedGaugeVec, LabelGuardedHistogramVec, LabelGuardedIntCounterVec, LabelGuardedIntGauge, LabelGuardedIntGaugeVec, TrAdderGauge, @@ -302,3 +303,37 @@ impl BatchManagerMetrics { Arc::new(GLOBAL_BATCH_MANAGER_METRICS.clone()) } } + +#[derive(Clone)] +pub struct BatchSpillMetrics { + pub batch_spill_read_bytes: GenericCounter, + pub batch_spill_write_bytes: GenericCounter, +} + +pub static GLOBAL_BATCH_SPILL_METRICS: LazyLock = + LazyLock::new(|| BatchSpillMetrics::new(&GLOBAL_METRICS_REGISTRY)); + +impl BatchSpillMetrics { + fn new(registry: &Registry) -> Self { + let batch_spill_read_bytes = register_int_counter_with_registry!( + "batch_spill_read_bytes", + "Total bytes of requests read from spill files", + registry, + ) + .unwrap(); + let batch_spill_write_bytes = register_int_counter_with_registry!( + "batch_spill_write_bytes", + "Total bytes of requests write to spill files", + registry, + ) + .unwrap(); + Self { + batch_spill_read_bytes, + batch_spill_write_bytes, + } + } + + pub fn for_test() -> Arc { + Arc::new(GLOBAL_BATCH_SPILL_METRICS.clone()) + } +} diff --git a/src/batch/src/rpc/service/task_service.rs b/src/batch/src/rpc/service/task_service.rs index 816b62a02fc4d..11ae64e7b3918 100644 --- a/src/batch/src/rpc/service/task_service.rs +++ b/src/batch/src/rpc/service/task_service.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::Into; use std::sync::Arc; use risingwave_common::util::tracing::TracingContext; diff --git a/src/connector/src/parser/maxwell/operators.rs b/src/batch/src/spill/mod.rs similarity index 81% rename from src/connector/src/parser/maxwell/operators.rs rename to src/batch/src/spill/mod.rs index 231399febc740..6af1eae7429c6 100644 --- a/src/connector/src/parser/maxwell/operators.rs +++ b/src/batch/src/spill/mod.rs @@ -12,6 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub const MAXWELL_INSERT_OP: &str = "insert"; -pub const MAXWELL_UPDATE_OP: &str = "update"; -pub const MAXWELL_DELETE_OP: &str = "delete"; +pub mod spill_op; diff --git a/src/batch/src/spill/spill_op.rs b/src/batch/src/spill/spill_op.rs new file mode 100644 index 0000000000000..237ee3baf0099 --- /dev/null +++ b/src/batch/src/spill/spill_op.rs @@ -0,0 +1,188 @@ +// Copyright 2024 RisingWave Labs +// +// 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::hash::BuildHasher; +use std::ops::{Deref, DerefMut}; +use std::sync::{Arc, LazyLock}; + +use anyhow::anyhow; +use futures_async_stream::try_stream; +use futures_util::AsyncReadExt; +use opendal::layers::RetryLayer; +use opendal::services::{Fs, Memory}; +use opendal::Operator; +use prost::Message; +use risingwave_common::array::DataChunk; +use risingwave_pb::data::DataChunk as PbDataChunk; +use thiserror_ext::AsReport; +use tokio::sync::Mutex; +use twox_hash::XxHash64; + +use crate::error::{BatchError, Result}; +use crate::monitor::BatchSpillMetrics; + +const RW_BATCH_SPILL_DIR_ENV: &str = "RW_BATCH_SPILL_DIR"; +pub const DEFAULT_SPILL_PARTITION_NUM: usize = 20; +const DEFAULT_SPILL_DIR: &str = "/tmp/"; +const RW_MANAGED_SPILL_DIR: &str = "/rw_batch_spill/"; +const DEFAULT_IO_BUFFER_SIZE: usize = 256 * 1024; +const DEFAULT_IO_CONCURRENT_TASK: usize = 8; + +#[derive(Clone)] +pub enum SpillBackend { + Disk, + /// Only for testing purpose + Memory, +} + +/// `SpillOp` is used to manage the spill directory of the spilling executor and it will drop the directory with a RAII style. +pub struct SpillOp { + pub op: Operator, +} + +impl SpillOp { + pub fn create(path: String, spill_backend: SpillBackend) -> Result { + assert!(path.ends_with('/')); + + let spill_dir = + std::env::var(RW_BATCH_SPILL_DIR_ENV).unwrap_or_else(|_| DEFAULT_SPILL_DIR.to_string()); + let root = format!("/{}/{}/{}/", spill_dir, RW_MANAGED_SPILL_DIR, path); + + let op = match spill_backend { + SpillBackend::Disk => { + let mut builder = Fs::default(); + builder.root(&root); + Operator::new(builder)? + .layer(RetryLayer::default()) + .finish() + } + SpillBackend::Memory => { + let mut builder = Memory::default(); + builder.root(&root); + Operator::new(builder)? + .layer(RetryLayer::default()) + .finish() + } + }; + Ok(SpillOp { op }) + } + + pub async fn clean_spill_directory() -> opendal::Result<()> { + static LOCK: LazyLock> = LazyLock::new(|| Mutex::new(0)); + let _guard = LOCK.lock().await; + + let spill_dir = + std::env::var(RW_BATCH_SPILL_DIR_ENV).unwrap_or_else(|_| DEFAULT_SPILL_DIR.to_string()); + let root = format!("/{}/{}/", spill_dir, RW_MANAGED_SPILL_DIR); + + let mut builder = Fs::default(); + builder.root(&root); + + let op: Operator = Operator::new(builder)? + .layer(RetryLayer::default()) + .finish(); + + op.remove_all("/").await + } + + pub async fn writer_with(&self, name: &str) -> Result { + Ok(self + .op + .writer_with(name) + .concurrent(DEFAULT_IO_CONCURRENT_TASK) + .chunk(DEFAULT_IO_BUFFER_SIZE) + .await?) + } + + pub async fn reader_with(&self, name: &str) -> Result { + Ok(self + .op + .reader_with(name) + .chunk(DEFAULT_IO_BUFFER_SIZE) + .await?) + } + + /// spill file content will look like the below. + /// + /// ```text + /// [proto_len] + /// [proto_bytes] + /// ... + /// [proto_len] + /// [proto_bytes] + /// ``` + #[try_stream(boxed, ok = DataChunk, error = BatchError)] + pub async fn read_stream(reader: opendal::Reader, spill_metrics: Arc) { + let mut reader = reader.into_futures_async_read(..).await?; + let mut buf = [0u8; 4]; + loop { + if let Err(err) = reader.read_exact(&mut buf).await { + if err.kind() == std::io::ErrorKind::UnexpectedEof { + break; + } else { + return Err(anyhow!(err).into()); + } + } + let len = u32::from_le_bytes(buf) as usize; + spill_metrics.batch_spill_read_bytes.inc_by(len as u64 + 4); + let mut buf = vec![0u8; len]; + reader.read_exact(&mut buf).await.map_err(|e| anyhow!(e))?; + let chunk_pb: PbDataChunk = Message::decode(buf.as_slice()).map_err(|e| anyhow!(e))?; + let chunk = DataChunk::from_protobuf(&chunk_pb)?; + yield chunk; + } + } +} + +impl Drop for SpillOp { + fn drop(&mut self) { + let op = self.op.clone(); + tokio::task::spawn(async move { + let result = op.remove_all("/").await; + if let Err(error) = result { + error!( + error = %error.as_report(), + "Failed to remove spill directory" + ); + } + }); + } +} + +impl DerefMut for SpillOp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.op + } +} + +impl Deref for SpillOp { + type Target = Operator; + + fn deref(&self) -> &Self::Target { + &self.op + } +} + +#[derive(Default, Clone, Copy)] +pub struct SpillBuildHasher(pub u64); + +impl BuildHasher for SpillBuildHasher { + type Hasher = XxHash64; + + fn build_hasher(&self) -> Self::Hasher { + XxHash64::with_seed(self.0) + } +} + +pub const SPILL_AT_LEAST_MEMORY: u64 = 1024 * 1024; diff --git a/src/batch/src/task/consistent_hash_shuffle_channel.rs b/src/batch/src/task/consistent_hash_shuffle_channel.rs index 3e42186fcf587..ad0fdbaa8b70a 100644 --- a/src/batch/src/task/consistent_hash_shuffle_channel.rs +++ b/src/batch/src/task/consistent_hash_shuffle_channel.rs @@ -13,13 +13,12 @@ // limitations under the License. use std::fmt::{Debug, Formatter}; -use std::option::Option; use std::sync::Arc; use anyhow::anyhow; use itertools::Itertools; use risingwave_common::array::DataChunk; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::hash::VirtualNode; use risingwave_pb::batch_plan::exchange_info::ConsistentHashInfo; use risingwave_pb::batch_plan::*; diff --git a/src/batch/src/task/context.rs b/src/batch/src/task/context.rs index 886eeb7d9753d..9fd998875d00e 100644 --- a/src/batch/src/task/context.rs +++ b/src/batch/src/task/context.rs @@ -11,7 +11,6 @@ // 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::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use prometheus::core::Atomic; @@ -27,7 +26,9 @@ use risingwave_storage::StateStoreImpl; use super::TaskId; use crate::error::Result; -use crate::monitor::{BatchMetricsWithTaskLabels, BatchMetricsWithTaskLabelsInner}; +use crate::monitor::{ + BatchMetricsWithTaskLabels, BatchMetricsWithTaskLabelsInner, BatchSpillMetrics, +}; use crate::task::{BatchEnvironment, TaskOutput, TaskOutputId}; use crate::worker_manager::worker_node_manager::WorkerNodeManagerRef; @@ -54,6 +55,8 @@ pub trait BatchTaskContext: Clone + Send + Sync + 'static { /// None indicates that not collect task metrics. fn batch_metrics(&self) -> Option; + fn spill_metrics(&self) -> Arc; + /// Get compute client pool. This is used in grpc exchange to avoid creating new compute client /// for each grpc call. fn client_pool(&self) -> ComputeClientPoolRef; @@ -63,10 +66,6 @@ pub trait BatchTaskContext: Clone + Send + Sync + 'static { fn source_metrics(&self) -> Arc; - fn store_mem_usage(&self, val: usize); - - fn mem_usage(&self) -> usize; - fn create_executor_mem_context(&self, executor_id: &str) -> MemoryContext; fn worker_node_manager(&self) -> Option; @@ -80,12 +79,6 @@ pub struct ComputeNodeContext { batch_metrics: Option, mem_context: MemoryContext, - - // Last mem usage value. Init to be 0. Should be the last value of `cur_mem_val`. - last_mem_val: Arc, - // How many memory bytes have been used in this task for the latest report value. Will be moved - // to `last_mem_val` if new value comes in. - cur_mem_val: Arc, } impl BatchTaskContext for ComputeNodeContext { @@ -115,6 +108,10 @@ impl BatchTaskContext for ComputeNodeContext { self.batch_metrics.clone() } + fn spill_metrics(&self) -> Arc { + self.env.spill_metrics() + } + fn client_pool(&self) -> ComputeClientPoolRef { self.env.client_pool() } @@ -127,22 +124,6 @@ impl BatchTaskContext for ComputeNodeContext { self.env.source_metrics() } - fn store_mem_usage(&self, val: usize) { - // Record the last mem val. - // Calculate the difference between old val and new value, and apply the diff to total - // memory usage value. - let old_value = self.cur_mem_val.load(Ordering::Relaxed); - self.last_mem_val.store(old_value, Ordering::Relaxed); - let diff = val as i64 - old_value as i64; - self.env.task_manager().apply_mem_diff(diff); - - self.cur_mem_val.store(val, Ordering::Relaxed); - } - - fn mem_usage(&self) -> usize { - self.cur_mem_val.load(Ordering::Relaxed) - } - fn create_executor_mem_context(&self, executor_id: &str) -> MemoryContext { if let Some(metrics) = &self.batch_metrics { let executor_mem_usage = metrics @@ -167,8 +148,6 @@ impl ComputeNodeContext { Self { env: BatchEnvironment::for_test(), batch_metrics: None, - cur_mem_val: Arc::new(0.into()), - last_mem_val: Arc::new(0.into()), mem_context: MemoryContext::none(), } } @@ -189,8 +168,6 @@ impl ComputeNodeContext { Self { env, batch_metrics: Some(batch_metrics), - cur_mem_val: Arc::new(0.into()), - last_mem_val: Arc::new(0.into()), mem_context, } } else { @@ -198,8 +175,6 @@ impl ComputeNodeContext { Self { env, batch_metrics: None, - cur_mem_val: Arc::new(0.into()), - last_mem_val: Arc::new(0.into()), mem_context: batch_mem_context, } } @@ -210,13 +185,7 @@ impl ComputeNodeContext { Self { env, batch_metrics: None, - cur_mem_val: Arc::new(0.into()), - last_mem_val: Arc::new(0.into()), mem_context: batch_mem_context, } } - - pub fn mem_usage(&self) -> usize { - self.cur_mem_val.load(Ordering::Relaxed) - } } diff --git a/src/batch/src/task/env.rs b/src/batch/src/task/env.rs index 58631cb9563a0..ecb7a3a8d3ebb 100644 --- a/src/batch/src/task/env.rs +++ b/src/batch/src/task/env.rs @@ -22,7 +22,9 @@ use risingwave_dml::dml_manager::DmlManagerRef; use risingwave_rpc_client::ComputeClientPoolRef; use risingwave_storage::StateStoreImpl; -use crate::monitor::{BatchExecutorMetrics, BatchManagerMetrics, BatchTaskMetrics}; +use crate::monitor::{ + BatchExecutorMetrics, BatchManagerMetrics, BatchSpillMetrics, BatchTaskMetrics, +}; use crate::task::BatchManager; /// The global environment for task execution. @@ -59,6 +61,9 @@ pub struct BatchEnvironment { /// Metrics for source. source_metrics: Arc, + /// Batch spill metrics + spill_metrics: Arc, + metric_level: MetricLevel, } @@ -75,6 +80,7 @@ impl BatchEnvironment { client_pool: ComputeClientPoolRef, dml_manager: DmlManagerRef, source_metrics: Arc, + spill_metrics: Arc, metric_level: MetricLevel, ) -> Self { BatchEnvironment { @@ -88,6 +94,7 @@ impl BatchEnvironment { client_pool, dml_manager, source_metrics, + spill_metrics, metric_level, } } @@ -103,6 +110,7 @@ impl BatchEnvironment { task_manager: Arc::new(BatchManager::new( BatchConfig::default(), BatchManagerMetrics::for_test(), + u64::MAX, )), server_addr: "127.0.0.1:5688".parse().unwrap(), config: Arc::new(BatchConfig::default()), @@ -115,6 +123,7 @@ impl BatchEnvironment { dml_manager: Arc::new(DmlManager::for_test()), source_metrics: Arc::new(SourceMetrics::default()), executor_metrics: Arc::new(BatchExecutorMetrics::for_test()), + spill_metrics: BatchSpillMetrics::for_test(), metric_level: MetricLevel::Debug, } } @@ -163,6 +172,10 @@ impl BatchEnvironment { self.source_metrics.clone() } + pub fn spill_metrics(&self) -> Arc { + self.spill_metrics.clone() + } + pub fn metric_level(&self) -> MetricLevel { self.metric_level } diff --git a/src/batch/src/task/hash_shuffle_channel.rs b/src/batch/src/task/hash_shuffle_channel.rs index 3965161030a32..ccea1529b40f6 100644 --- a/src/batch/src/task/hash_shuffle_channel.rs +++ b/src/batch/src/task/hash_shuffle_channel.rs @@ -13,12 +13,11 @@ // limitations under the License. use std::fmt::{Debug, Formatter}; -use std::option::Option; use std::sync::Arc; use anyhow::anyhow; use risingwave_common::array::DataChunk; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::util::hash_util::Crc32FastBuilder; use risingwave_pb::batch_plan::exchange_info::HashInfo; use risingwave_pb::batch_plan::*; diff --git a/src/batch/src/task/task_execution.rs b/src/batch/src/task/task_execution.rs index 7b5e00df18c03..4536dad1c031f 100644 --- a/src/batch/src/task/task_execution.rs +++ b/src/batch/src/task/task_execution.rs @@ -79,7 +79,7 @@ impl StateReporter { #[derive(PartialEq, Eq, Hash, Clone, Debug, Default)] pub struct TaskId { - pub task_id: u32, + pub task_id: u64, pub stage_id: u32, pub query_id: String, } @@ -87,7 +87,7 @@ pub struct TaskId { #[derive(PartialEq, Eq, Hash, Clone, Default)] pub struct TaskOutputId { pub task_id: TaskId, - pub output_id: u32, + pub output_id: u64, } /// More compact formatter compared to derived `fmt::Debug`. @@ -559,8 +559,8 @@ impl BatchTaskExecution { } } } - date_chunk = data_chunk_stream.next()=> { - match date_chunk { + data_chunk = data_chunk_stream.next()=> { + match data_chunk { Some(Ok(data_chunk)) => { if let Err(e) = sender.send(data_chunk).await { match e { @@ -691,10 +691,6 @@ impl BatchTaskExecution { } } - pub fn mem_usage(&self) -> usize { - self.context.mem_usage() - } - /// Check the task status: whether has ended. pub fn is_end(&self) -> bool { let guard = self.state.lock(); diff --git a/src/batch/src/task/task_manager.rs b/src/batch/src/task/task_manager.rs index e24fb7b09defd..f25ce88379905 100644 --- a/src/batch/src/task/task_manager.rs +++ b/src/batch/src/task/task_manager.rs @@ -17,7 +17,6 @@ use std::net::SocketAddr; use std::sync::Arc; use anyhow::Context; -use hytra::TrAdder; use parking_lot::Mutex; use risingwave_common::config::BatchConfig; use risingwave_common::memory::MemoryContext; @@ -50,11 +49,6 @@ pub struct BatchManager { /// Batch configuration config: BatchConfig, - /// Total batch memory usage in this CN. - /// When each task context report their own usage, it will apply the diff into this total mem - /// value for all tasks. - total_mem_val: Arc>, - /// Memory context used for batch tasks in cn. mem_context: MemoryContext, @@ -63,7 +57,7 @@ pub struct BatchManager { } impl BatchManager { - pub fn new(config: BatchConfig, metrics: Arc) -> Self { + pub fn new(config: BatchConfig, metrics: Arc, mem_limit: u64) -> Self { let runtime = { let mut builder = tokio::runtime::Builder::new_multi_thread(); if let Some(worker_threads_num) = config.worker_threads_num { @@ -76,12 +70,11 @@ impl BatchManager { .unwrap() }; - let mem_context = MemoryContext::root(metrics.batch_total_mem.clone()); + let mem_context = MemoryContext::root(metrics.batch_total_mem.clone(), mem_limit); BatchManager { tasks: Arc::new(Mutex::new(HashMap::new())), runtime: Arc::new(runtime.into()), config, - total_mem_val: TrAdder::new().into(), metrics, mem_context, } @@ -288,45 +281,6 @@ impl BatchManager { pub fn config(&self) -> &BatchConfig { &self.config } - - /// Kill batch queries with larges memory consumption per task. Required to maintain task level - /// memory usage in the struct. Will be called by global memory manager. - pub fn kill_queries(&self, reason: String) { - let mut max_mem_task_id = None; - let mut max_mem = usize::MIN; - let guard = self.tasks.lock(); - for (t_id, t) in &*guard { - // If the task has been stopped, we should not count this. - if t.is_end() { - continue; - } - // Alternatively, we can use a bool flag to indicate end of execution. - // Now we use only store 0 bytes in Context after execution ends. - let mem_usage = t.mem_usage(); - if mem_usage > max_mem { - max_mem = mem_usage; - max_mem_task_id = Some(t_id.clone()); - } - } - if let Some(id) = max_mem_task_id { - let t = guard.get(&id).unwrap(); - // FIXME: `Abort` will not report error but truncated results to user. We should - // consider throw error. - t.abort(reason); - } - } - - /// Called by global memory manager for total usage of batch tasks. This op is designed to be - /// light-weight - pub fn total_mem_usage(&self) -> usize { - self.total_mem_val.get() as usize - } - - /// Calculate the diff between this time and last time memory usage report, apply the diff for - /// the global counter. Due to the limitation of hytra, we need to use i64 type here. - pub fn apply_mem_diff(&self, diff: i64) { - self.total_mem_val.inc(diff) - } } #[cfg(test)] @@ -343,11 +297,12 @@ mod tests { use crate::monitor::BatchManagerMetrics; use crate::task::{BatchManager, TaskId}; - #[test] - fn test_task_not_found() { + #[tokio::test] + async fn test_task_not_found() { let manager = Arc::new(BatchManager::new( BatchConfig::default(), BatchManagerMetrics::for_test(), + u64::MAX, )); let task_id = TaskId { task_id: 0, @@ -375,6 +330,7 @@ mod tests { let manager = Arc::new(BatchManager::new( BatchConfig::default(), BatchManagerMetrics::for_test(), + u64::MAX, )); let plan = PlanFragment { root: Some(PlanNode { @@ -415,6 +371,7 @@ mod tests { let manager = Arc::new(BatchManager::new( BatchConfig::default(), BatchManagerMetrics::for_test(), + u64::MAX, )); let plan = PlanFragment { root: Some(PlanNode { @@ -445,6 +402,7 @@ mod tests { let manager = Arc::new(BatchManager::new( BatchConfig::default(), BatchManagerMetrics::for_test(), + u64::MAX, )); let plan = PlanFragment { root: Some(PlanNode { diff --git a/src/batch/src/worker_manager/worker_node_manager.rs b/src/batch/src/worker_manager/worker_node_manager.rs index 5b0813186fd1c..0fc3d539101bb 100644 --- a/src/batch/src/worker_manager/worker_node_manager.rs +++ b/src/batch/src/worker_manager/worker_node_manager.rs @@ -18,8 +18,7 @@ use std::time::Duration; use rand::seq::SliceRandom; use risingwave_common::bail; -use risingwave_common::hash::{ParallelUnitId, ParallelUnitMapping}; -use risingwave_common::util::worker_util::get_pu_to_worker_mapping; +use risingwave_common::hash::{WorkerSlotId, WorkerSlotMapping}; use risingwave_common::vnode_mapping::vnode_placement::place_vnode; use risingwave_pb::common::{WorkerNode, WorkerType}; @@ -36,12 +35,10 @@ pub struct WorkerNodeManager { struct WorkerNodeManagerInner { worker_nodes: Vec, - /// A cache for parallel units to worker nodes. It should be consistent with `worker_nodes`. - pu_to_worker: HashMap, /// fragment vnode mapping info for streaming - streaming_fragment_vnode_mapping: HashMap, + streaming_fragment_vnode_mapping: HashMap, /// fragment vnode mapping info for serving - serving_fragment_vnode_mapping: HashMap, + serving_fragment_vnode_mapping: HashMap, } pub type WorkerNodeManagerRef = Arc; @@ -57,7 +54,6 @@ impl WorkerNodeManager { Self { inner: RwLock::new(WorkerNodeManagerInner { worker_nodes: Default::default(), - pu_to_worker: Default::default(), streaming_fragment_vnode_mapping: Default::default(), serving_fragment_vnode_mapping: Default::default(), }), @@ -68,7 +64,6 @@ impl WorkerNodeManager { /// Used in tests. pub fn mock(worker_nodes: Vec) -> Self { let inner = RwLock::new(WorkerNodeManagerInner { - pu_to_worker: get_pu_to_worker_mapping(&worker_nodes), worker_nodes, streaming_fragment_vnode_mapping: HashMap::new(), serving_fragment_vnode_mapping: HashMap::new(), @@ -120,23 +115,18 @@ impl WorkerNodeManager { *w = node; } } - // Update `pu_to_worker` - write_guard.pu_to_worker = get_pu_to_worker_mapping(&write_guard.worker_nodes); } pub fn remove_worker_node(&self, node: WorkerNode) { let mut write_guard = self.inner.write().unwrap(); write_guard.worker_nodes.retain(|x| x.id != node.id); - - // Update `pu_to_worker` - write_guard.pu_to_worker = get_pu_to_worker_mapping(&write_guard.worker_nodes); } pub fn refresh( &self, nodes: Vec, - streaming_mapping: HashMap, - serving_mapping: HashMap, + streaming_mapping: HashMap, + serving_mapping: HashMap, ) { let mut write_guard = self.inner.write().unwrap(); tracing::debug!("Refresh worker nodes {:?}.", nodes); @@ -149,42 +139,50 @@ impl WorkerNodeManager { serving_mapping.keys() ); write_guard.worker_nodes = nodes; - // Update `pu_to_worker` - write_guard.pu_to_worker = get_pu_to_worker_mapping(&write_guard.worker_nodes); write_guard.streaming_fragment_vnode_mapping = streaming_mapping; write_guard.serving_fragment_vnode_mapping = serving_mapping; } - /// If parallel unit ids is empty, the scheduler may fail to schedule any task and stuck at + /// If worker slot ids is empty, the scheduler may fail to schedule any task and stuck at /// schedule next stage. If we do not return error in this case, needs more complex control /// logic above. Report in this function makes the schedule root fail reason more clear. - pub fn get_workers_by_parallel_unit_ids( + pub fn get_workers_by_worker_slot_ids( &self, - parallel_unit_ids: &[ParallelUnitId], + worker_slot_ids: &[WorkerSlotId], ) -> Result> { - if parallel_unit_ids.is_empty() { + if worker_slot_ids.is_empty() { return Err(BatchError::EmptyWorkerNodes); } let guard = self.inner.read().unwrap(); - let mut workers = Vec::with_capacity(parallel_unit_ids.len()); - for parallel_unit_id in parallel_unit_ids { - match guard.pu_to_worker.get(parallel_unit_id) { - Some(worker) => workers.push(worker.clone()), + let worker_slot_index: HashMap<_, _> = guard + .worker_nodes + .iter() + .flat_map(|worker| { + (0..worker.parallelism()).map(move |i| (WorkerSlotId::new(worker.id, i), worker)) + }) + .collect(); + + let mut workers = Vec::with_capacity(worker_slot_ids.len()); + + for worker_slot_id in worker_slot_ids { + match worker_slot_index.get(worker_slot_id) { + Some(worker) => workers.push((*worker).clone()), None => bail!( - "No worker node found for parallel unit id: {}", - parallel_unit_id + "No worker node found for worker slot id: {}", + worker_slot_id ), } } + Ok(workers) } pub fn get_streaming_fragment_mapping( &self, fragment_id: &FragmentId, - ) -> Result { + ) -> Result { self.inner .read() .unwrap() @@ -197,7 +195,7 @@ impl WorkerNodeManager { pub fn insert_streaming_fragment_mapping( &self, fragment_id: FragmentId, - vnode_mapping: ParallelUnitMapping, + vnode_mapping: WorkerSlotMapping, ) { self.inner .write() @@ -210,7 +208,7 @@ impl WorkerNodeManager { pub fn update_streaming_fragment_mapping( &self, fragment_id: FragmentId, - vnode_mapping: ParallelUnitMapping, + vnode_mapping: WorkerSlotMapping, ) { let mut guard = self.inner.write().unwrap(); guard @@ -228,7 +226,7 @@ impl WorkerNodeManager { } /// Returns fragment's vnode mapping for serving. - fn serving_fragment_mapping(&self, fragment_id: FragmentId) -> Result { + fn serving_fragment_mapping(&self, fragment_id: FragmentId) -> Result { self.inner .read() .unwrap() @@ -236,7 +234,7 @@ impl WorkerNodeManager { .ok_or_else(|| BatchError::ServingVnodeMappingNotFound(fragment_id)) } - pub fn set_serving_fragment_mapping(&self, mappings: HashMap) { + pub fn set_serving_fragment_mapping(&self, mappings: HashMap) { let mut guard = self.inner.write().unwrap(); tracing::debug!( "Set serving vnode mapping for fragments {:?}", @@ -247,7 +245,7 @@ impl WorkerNodeManager { pub fn upsert_serving_fragment_mapping( &self, - mappings: HashMap, + mappings: HashMap, ) { let mut guard = self.inner.write().unwrap(); tracing::debug!( @@ -299,7 +297,7 @@ impl WorkerNodeManager { } impl WorkerNodeManagerInner { - fn get_serving_fragment_mapping(&self, fragment_id: FragmentId) -> Option { + fn get_serving_fragment_mapping(&self, fragment_id: FragmentId) -> Option { self.serving_fragment_vnode_mapping .get(&fragment_id) .cloned() @@ -336,13 +334,10 @@ impl WorkerNodeSelector { } else { self.apply_worker_node_mask(self.manager.list_serving_worker_nodes()) }; - worker_nodes - .iter() - .map(|node| node.parallel_units.len()) - .sum() + worker_nodes.iter().map(|node| node.parallelism()).sum() } - pub fn fragment_mapping(&self, fragment_id: FragmentId) -> Result { + pub fn fragment_mapping(&self, fragment_id: FragmentId) -> Result { if self.enable_barrier_read { self.manager.get_streaming_fragment_mapping(&fragment_id) } else { diff --git a/src/bench/Cargo.toml b/src/bench/Cargo.toml index f5c36c6b9adc2..32dcdaa2a5df6 100644 --- a/src/bench/Cargo.toml +++ b/src/bench/Cargo.toml @@ -25,6 +25,7 @@ itertools = { workspace = true } libc = "0.2" opentelemetry = { workspace = true, optional = true } parking_lot = { workspace = true } +plotlib = "0.5.1" prometheus = { version = "0.13", features = ["process"] } rand = { workspace = true } risingwave_common = { workspace = true } @@ -54,7 +55,7 @@ tracing-subscriber = "0.3.17" workspace-hack = { path = "../workspace-hack" } [target.'cfg(target_os = "linux")'.dependencies] -nix = { version = "0.28", features = ["fs", "mman"] } +nix = { version = "0.29", features = ["fs", "mman"] } [[bin]] name = "s3-bench" diff --git a/src/bench/sink_bench/main.rs b/src/bench/sink_bench/main.rs index 91ebe6bd44d0f..9eea1c94655ba 100644 --- a/src/bench/sink_bench/main.rs +++ b/src/bench/sink_bench/main.rs @@ -17,16 +17,25 @@ #![feature(let_chains)] use core::str::FromStr; -use std::collections::HashMap; +use core::sync::atomic::Ordering; +use std::collections::{BTreeMap, HashMap}; +use std::sync::atomic::AtomicU64; +use std::sync::Arc; use anyhow::anyhow; use clap::Parser; +use futures::channel::oneshot; use futures::prelude::future::Either; use futures::prelude::stream::{BoxStream, PollNext}; use futures::stream::select_with_strategy; use futures::{FutureExt, StreamExt, TryStreamExt}; use futures_async_stream::try_stream; -use risingwave_common::buffer::Bitmap; +use itertools::Itertools; +use plotlib::page::Page; +use plotlib::repr::Plot; +use plotlib::style::{LineJoin, LineStyle}; +use plotlib::view::ContinuousView; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::ColumnId; use risingwave_connector::dispatch_sink; use risingwave_connector::parser::{ @@ -49,16 +58,15 @@ use risingwave_connector::source::datagen::{ use risingwave_connector::source::{ Column, DataType, SourceContext, SourceEnumeratorContext, SplitEnumerator, SplitReader, }; -use risingwave_pb::connector_service::SinkPayloadFormat; use risingwave_stream::executor::test_utils::prelude::ColumnDesc; use risingwave_stream::executor::{Barrier, Message, MessageStreamItem, StreamExecutorError}; use serde::{Deserialize, Deserializer}; use thiserror_ext::AsReport; use tokio::sync::oneshot::Sender; -use tokio::time::{sleep, Instant}; +use tokio::time::sleep; const CHECKPOINT_INTERVAL: u64 = 1000; -const THROUGHPUT_METRIC_RECORD_INTERVAL: u128 = 500; +const THROUGHPUT_METRIC_RECORD_INTERVAL: u64 = 500; const BENCH_TIME: u64 = 20; const BENCH_TEST: &str = "bench_test"; @@ -116,7 +124,7 @@ impl LogReader for MockRangeLogReader { } } - async fn truncate(&mut self, _offset: TruncateOffset) -> LogStoreResult<()> { + fn truncate(&mut self, _offset: TruncateOffset) -> LogStoreResult<()> { Ok(()) } @@ -144,47 +152,105 @@ impl MockRangeLogReader { } struct ThroughputMetric { - chunk_size_list: Vec<(u64, Instant)>, - accumulate_chunk_size: u64, - // Record every `record_interval` ms - record_interval: u128, - last_record_time: Instant, + accumulate_chunk_size: Arc, + stop_tx: oneshot::Sender<()>, + vec_rx: oneshot::Receiver>, } impl ThroughputMetric { pub fn new() -> Self { + let (stop_tx, mut stop_rx) = oneshot::channel::<()>(); + let (vec_tx, vec_rx) = oneshot::channel::>(); + let accumulate_chunk_size = Arc::new(AtomicU64::new(0)); + let accumulate_chunk_size_clone = accumulate_chunk_size.clone(); + tokio::spawn(async move { + let mut chunk_size_list = vec![]; + loop { + tokio::select! { + _ = sleep(tokio::time::Duration::from_millis( + THROUGHPUT_METRIC_RECORD_INTERVAL, + )) => { + chunk_size_list.push(accumulate_chunk_size_clone.load(Ordering::Relaxed)); + } + _ = &mut stop_rx => { + vec_tx.send(chunk_size_list).unwrap(); + break; + } + } + } + }); + Self { - chunk_size_list: vec![], - accumulate_chunk_size: 0, - record_interval: THROUGHPUT_METRIC_RECORD_INTERVAL, - last_record_time: Instant::now(), + accumulate_chunk_size, + stop_tx, + vec_rx, } } pub fn add_metric(&mut self, chunk_size: usize) { - self.accumulate_chunk_size += chunk_size as u64; - if Instant::now() - .duration_since(self.last_record_time) - .as_millis() - > self.record_interval - { - self.chunk_size_list - .push((self.accumulate_chunk_size, Instant::now())); - self.last_record_time = Instant::now(); - } + self.accumulate_chunk_size + .fetch_add(chunk_size as u64, Ordering::Relaxed); } - pub fn get_throughput(&self) -> Vec { + pub async fn print_throughput(self) { + self.stop_tx.send(()).unwrap(); + let throughput_sum_vec = self.vec_rx.await.unwrap(); #[allow(clippy::disallowed_methods)] - self.chunk_size_list + let throughput_vec = throughput_sum_vec + .iter() + .zip(throughput_sum_vec.iter().skip(1)) + .map(|(current, next)| (next - current) * 1000 / THROUGHPUT_METRIC_RECORD_INTERVAL) + .collect_vec(); + if throughput_vec.is_empty() { + println!("Throughput Sink: Don't get Throughput, please check"); + return; + } + let avg = throughput_vec.iter().sum::() / throughput_vec.len() as u64; + let throughput_vec_sorted = throughput_vec.iter().sorted().collect_vec(); + let p90 = throughput_vec_sorted[throughput_vec_sorted.len() * 90 / 100]; + let p95 = throughput_vec_sorted[throughput_vec_sorted.len() * 95 / 100]; + let p99 = throughput_vec_sorted[throughput_vec_sorted.len() * 99 / 100]; + println!("Throughput Sink:"); + println!("avg: {:?} rows/s ", avg); + println!("p90: {:?} rows/s ", p90); + println!("p95: {:?} rows/s ", p95); + println!("p99: {:?} rows/s ", p99); + let draw_vec: Vec<(f64, f64)> = throughput_vec .iter() - .zip(self.chunk_size_list.iter().skip(1)) - .map(|(current, next)| { - let throughput = (next.0 - current.0) * 1000 - / (next.1.duration_since(current.1).as_millis() as u64); - format!("{} rows/s", throughput) + .enumerate() + .map(|(index, &value)| { + ( + (index as f64) * (THROUGHPUT_METRIC_RECORD_INTERVAL as f64 / 1000_f64), + value as f64, + ) }) - .collect() + .collect(); + + let s1: Plot = Plot::new(draw_vec).line_style( + LineStyle::new() + .colour("burlywood") + .linejoin(LineJoin::Round), + ); + + let v = ContinuousView::new() + .add(s1) + .x_range(0.0, BENCH_TIME as f64) + .y_range( + **throughput_vec_sorted.first().unwrap() as f64, + **throughput_vec_sorted.last().unwrap() as f64, + ) + .x_label("Time (s)") + .y_label("Throughput (rows/s)"); + + Page::single(&v).save("throughput.svg").unwrap(); + + println!( + "Throughput Sink: {:?}", + throughput_vec + .iter() + .map(|a| format!("{} rows/s", a)) + .collect_vec() + ); } } @@ -210,7 +276,6 @@ impl MockDatagenSource { .unwrap(); let parser_config = ParserConfig { specific: SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Native, protocol_config: ProtocolProperties::Native, }, @@ -358,9 +423,9 @@ fn read_table_schema_from_yml(path: &str) -> TableSchemaFromYml { table } -fn read_sink_option_from_yml(path: &str) -> HashMap> { +fn read_sink_option_from_yml(path: &str) -> HashMap> { let data = std::fs::read_to_string(path).unwrap(); - let sink_option: HashMap> = + let sink_option: HashMap> = serde_yaml::from_str(&data).unwrap(); sink_option } @@ -404,21 +469,13 @@ fn mock_from_legacy_type( format, encode: SinkEncode::Json, options: Default::default(), + key_encode: None, })) } else { SinkFormatDesc::from_legacy_type(connector, r#type) } } -fn print_throughput_result(throughput_metric: ThroughputMetric) { - let throughput_result = throughput_metric.get_throughput(); - if throughput_result.is_empty() { - println!("Throughput Sink: Don't get Throughput, please check"); - } else { - println!("Throughput Sink: {:?}", throughput_result); - } -} - #[tokio::main] async fn main() { let cfg = Config::parse(); @@ -471,9 +528,8 @@ async fn main() { sink_from_name: "not_need_set".to_string(), }; let sink = build_sink(sink_param).unwrap(); - let mut sink_writer_param = SinkWriterParam::for_test(); + let sink_writer_param = SinkWriterParam::for_test(); println!("Start Sink Bench!, Wait {:?}s", BENCH_TIME); - sink_writer_param.connector_params.sink_payload_format = SinkPayloadFormat::StreamChunk; tokio::spawn(async move { dispatch_sink!(sink, sink, { consume_log_stream(sink, mock_range_log_reader, sink_writer_param).boxed() @@ -485,5 +541,5 @@ async fn main() { sleep(tokio::time::Duration::from_secs(BENCH_TIME)).await; println!("Bench Over!"); stop_tx.send(()).await.unwrap(); - print_throughput_result(data_size_rx.await.unwrap()); + data_size_rx.await.unwrap().print_throughput().await; } diff --git a/src/cmd/src/lib.rs b/src/cmd/src/lib.rs index 59ffda9e76557..0e711458d5196 100644 --- a/src/cmd/src/lib.rs +++ b/src/cmd/src/lib.rs @@ -37,27 +37,29 @@ risingwave_expr_impl::enable!(); // Entry point functions. -pub fn compute(opts: ComputeNodeOpts) { +pub fn compute(opts: ComputeNodeOpts) -> ! { init_risingwave_logger(LoggerSettings::from_opts(&opts)); - main_okk(risingwave_compute::start(opts)); + main_okk(|shutdown| risingwave_compute::start(opts, shutdown)); } -pub fn meta(opts: MetaNodeOpts) { +pub fn meta(opts: MetaNodeOpts) -> ! { init_risingwave_logger(LoggerSettings::from_opts(&opts)); - main_okk(risingwave_meta_node::start(opts)); + // TODO(shutdown): pass the shutdown token + main_okk(|_| risingwave_meta_node::start(opts)); } -pub fn frontend(opts: FrontendOpts) { +pub fn frontend(opts: FrontendOpts) -> ! { init_risingwave_logger(LoggerSettings::from_opts(&opts)); - main_okk(risingwave_frontend::start(opts)); + main_okk(|shutdown| risingwave_frontend::start(opts, shutdown)); } -pub fn compactor(opts: CompactorOpts) { +pub fn compactor(opts: CompactorOpts) -> ! { init_risingwave_logger(LoggerSettings::from_opts(&opts)); - main_okk(risingwave_compactor::start(opts)); + // TODO(shutdown): pass the shutdown token + main_okk(|_| risingwave_compactor::start(opts)); } -pub fn ctl(opts: CtlOpts) { +pub fn ctl(opts: CtlOpts) -> ! { init_risingwave_logger(LoggerSettings::new("ctl").stderr(true)); - main_okk(risingwave_ctl::start(opts)); + main_okk(|shutdown| risingwave_ctl::start(opts, shutdown)); } diff --git a/src/cmd_all/Cargo.toml b/src/cmd_all/Cargo.toml index 5ed92b65609cc..d3341b3137d43 100644 --- a/src/cmd_all/Cargo.toml +++ b/src/cmd_all/Cargo.toml @@ -8,11 +8,15 @@ license = { workspace = true } repository = { workspace = true } [features] +default = ["rw-static-link"] rw-static-link = ["workspace-config/rw-static-link"] rw-dynamic-link = ["workspace-config/rw-dynamic-link"] -embedded-deno-udf = ["risingwave_expr/embedded-deno-udf"] -embedded-python-udf = ["risingwave_expr/embedded-python-udf"] -default = ["rw-static-link"] +all-udf = ["external-udf", "wasm-udf", "js-udf", "deno-udf", "python-udf"] +external-udf = ["risingwave_expr_impl/external-udf"] +wasm-udf = ["risingwave_expr_impl/wasm-udf"] +js-udf = ["risingwave_expr_impl/js-udf"] +deno-udf = ["risingwave_expr_impl/deno-udf"] +python-udf = ["risingwave_expr_impl/python-udf"] [package.metadata.cargo-machete] ignored = ["workspace-hack", "workspace-config", "task_stats_alloc"] @@ -32,7 +36,6 @@ risingwave_common = { workspace = true } risingwave_compactor = { workspace = true } risingwave_compute = { workspace = true } risingwave_ctl = { workspace = true } -risingwave_expr = { workspace = true } risingwave_expr_impl = { workspace = true } risingwave_frontend = { workspace = true } risingwave_meta_node = { workspace = true } diff --git a/src/cmd_all/scripts/e2e-full-standalone-demo.sh b/src/cmd_all/scripts/e2e-full-standalone-demo.sh index 28469aaddbe70..409c543e03afa 100755 --- a/src/cmd_all/scripts/e2e-full-standalone-demo.sh +++ b/src/cmd_all/scripts/e2e-full-standalone-demo.sh @@ -20,17 +20,14 @@ set -euo pipefail insert_json_kafka() { - echo $1 | \ - $KAFKA_PATH/bin/kafka-console-producer.sh \ - --topic source_kafka \ - --bootstrap-server localhost:29092 + echo $1 | + RPK_BROKERS=localhost:29092 \ + rpk topic produce source_kafka -f "%v" } create_topic_kafka() { - "$KAFKA_PATH"/bin/kafka-topics.sh \ - --create \ - --topic source_kafka \ - --bootstrap-server localhost:29092 + RPK_BROKERS=localhost:29092 \ + rpk topic create source_kafka } # Make sure we start on clean cluster diff --git a/src/cmd_all/src/bin/risingwave.rs b/src/cmd_all/src/bin/risingwave.rs index c95e7a61db4d3..13c73217b77cc 100644 --- a/src/cmd_all/src/bin/risingwave.rs +++ b/src/cmd_all/src/bin/risingwave.rs @@ -15,6 +15,7 @@ #![feature(assert_matches)] #![cfg_attr(coverage, feature(coverage_attribute))] +use std::env; use std::ffi::OsString; use std::str::FromStr; @@ -23,6 +24,7 @@ use clap::{command, ArgMatches, Args, Command, CommandFactory, FromArgMatches}; use risingwave_cmd::{compactor, compute, ctl, frontend, meta}; use risingwave_cmd_all::{SingleNodeOpts, StandaloneOpts}; use risingwave_common::git_sha; +use risingwave_common::telemetry::{TELEMETRY_CLUSTER_TYPE, TELEMETRY_CLUSTER_TYPE_SINGLE_NODE}; use risingwave_compactor::CompactorOpts; use risingwave_compute::ComputeNodeOpts; use risingwave_ctl::CliOpts as CtlOpts; @@ -108,7 +110,7 @@ enum Component { impl Component { /// Start the component from the given `args` without `argv[0]`. - fn start(self, matches: &ArgMatches) { + fn start(self, matches: &ArgMatches) -> ! { eprintln!("launching `{}`", self); fn parse_opts(matches: &ArgMatches) -> T { @@ -222,25 +224,30 @@ fn main() { component.start(&matches); } -fn standalone(opts: StandaloneOpts) { +fn standalone(opts: StandaloneOpts) -> ! { let opts = risingwave_cmd_all::parse_standalone_opt_args(&opts); let settings = risingwave_rt::LoggerSettings::from_opts(&opts) .with_target("risingwave_storage", Level::WARN) .with_thread_name(true); risingwave_rt::init_risingwave_logger(settings); - risingwave_rt::main_okk(risingwave_cmd_all::standalone(opts)).unwrap(); + // TODO(shutdown): pass the shutdown token + risingwave_rt::main_okk(|_| risingwave_cmd_all::standalone(opts)); } /// For single node, the internals are just a config mapping from its /// high level options to standalone mode node-level options. /// We will start a standalone instance, with all nodes in the same process. -fn single_node(opts: SingleNodeOpts) { +fn single_node(opts: SingleNodeOpts) -> ! { + if env::var(TELEMETRY_CLUSTER_TYPE).is_err() { + env::set_var(TELEMETRY_CLUSTER_TYPE, TELEMETRY_CLUSTER_TYPE_SINGLE_NODE); + } let opts = risingwave_cmd_all::map_single_node_opts_to_standalone_opts(opts); let settings = risingwave_rt::LoggerSettings::from_opts(&opts) .with_target("risingwave_storage", Level::WARN) .with_thread_name(true); risingwave_rt::init_risingwave_logger(settings); - risingwave_rt::main_okk(risingwave_cmd_all::standalone(opts)).unwrap(); + // TODO(shutdown): pass the shutdown token + risingwave_rt::main_okk(|_| risingwave_cmd_all::standalone(opts)); } #[cfg(test)] diff --git a/src/cmd_all/src/single_node.rs b/src/cmd_all/src/single_node.rs index 63099c1ff640c..5495be878202b 100644 --- a/src/cmd_all/src/single_node.rs +++ b/src/cmd_all/src/single_node.rs @@ -173,7 +173,10 @@ pub fn map_single_node_opts_to_standalone_opts(opts: SingleNodeOpts) -> ParsedSt // Set meta store for meta (if not set). It could be set by environment variables before this. let meta_backend_is_set = match meta_opts.backend { Some(MetaBackend::Etcd) => !meta_opts.etcd_endpoints.is_empty(), - Some(MetaBackend::Sql) => meta_opts.sql_endpoint.is_some(), + Some(MetaBackend::Sql) + | Some(MetaBackend::Sqlite) + | Some(MetaBackend::Postgres) + | Some(MetaBackend::Mysql) => meta_opts.sql_endpoint.is_some(), Some(MetaBackend::Mem) => true, None => false, }; @@ -181,12 +184,11 @@ pub fn map_single_node_opts_to_standalone_opts(opts: SingleNodeOpts) -> ParsedSt if opts.in_memory { meta_opts.backend = Some(MetaBackend::Mem); } else { - meta_opts.backend = Some(MetaBackend::Sql); + meta_opts.backend = Some(MetaBackend::Sqlite); let meta_store_dir = format!("{}/meta_store", &store_directory); std::fs::create_dir_all(&meta_store_dir).unwrap(); - let meta_store_endpoint = - format!("sqlite://{}/single_node.db?mode=rwc", &meta_store_dir); - meta_opts.sql_endpoint = Some(meta_store_endpoint); + let meta_store_endpoint = format!("{}/single_node.db", &meta_store_dir); + meta_opts.sql_endpoint = Some(meta_store_endpoint.into()); } } diff --git a/src/cmd_all/src/standalone.rs b/src/cmd_all/src/standalone.rs index 90ba35b99d626..791921fc07d69 100644 --- a/src/cmd_all/src/standalone.rs +++ b/src/cmd_all/src/standalone.rs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use anyhow::Result; use clap::Parser; use risingwave_common::config::MetaBackend; use risingwave_common::util::meta_addr::MetaAddressStrategy; +use risingwave_common::util::tokio_util::sync::CancellationToken; use risingwave_compactor::CompactorOpts; use risingwave_compute::ComputeNodeOpts; use risingwave_frontend::FrontendOpts; @@ -183,9 +183,12 @@ pub async fn standalone( frontend_opts, compactor_opts, }: ParsedStandaloneOpts, -) -> Result<()> { +) { tracing::info!("launching Risingwave in standalone mode"); + // TODO(shutdown): use the real one passed-in + let shutdown = CancellationToken::new(); + let mut is_in_memory = false; if let Some(opts) = meta_opts { is_in_memory = matches!(opts.backend, Some(MetaBackend::Mem)); @@ -215,11 +218,15 @@ pub async fn standalone( } if let Some(opts) = compute_opts { tracing::info!("starting compute-node thread with cli args: {:?}", opts); - let _compute_handle = tokio::spawn(async move { risingwave_compute::start(opts).await }); + let shutdown = shutdown.clone(); + let _compute_handle = + tokio::spawn(async move { risingwave_compute::start(opts, shutdown).await }); } if let Some(opts) = frontend_opts.clone() { tracing::info!("starting frontend-node thread with cli args: {:?}", opts); - let _frontend_handle = tokio::spawn(async move { risingwave_frontend::start(opts).await }); + let shutdown = shutdown.clone(); + let _frontend_handle = + tokio::spawn(async move { risingwave_frontend::start(opts, shutdown).await }); } if let Some(opts) = compactor_opts { tracing::info!("starting compactor-node thread with cli args: {:?}", opts); @@ -265,8 +272,6 @@ It SHOULD NEVER be used in benchmarks and production environment!!!" // support it? signal::ctrl_c().await.unwrap(); tracing::info!("Ctrl+C received, now exiting"); - - Ok(()) } #[cfg(test)] @@ -323,6 +328,9 @@ mod test { etcd_username: "", etcd_password: [REDACTED alloc::string::String], sql_endpoint: None, + sql_username: "", + sql_password: [REDACTED alloc::string::String], + sql_database: "", prometheus_endpoint: None, prometheus_selector: None, privatelink_endpoint_default_tags: None, @@ -356,9 +364,9 @@ mod test { http://127.0.0.1:5690/, ], ), - connector_rpc_sink_payload_format: None, config_path: "src/config/test.toml", total_memory_bytes: 34359738368, + reserved_memory_bytes: None, parallelism: 10, role: Both, metrics_level: None, @@ -371,7 +379,7 @@ mod test { ), frontend_opts: Some( FrontendOpts { - listen_addr: "127.0.0.1:4566", + listen_addr: "0.0.0.0:4566", advertise_addr: None, meta_addr: List( [ diff --git a/src/common/Cargo.toml b/src/common/Cargo.toml index d21e276089c2c..3ae8fb38fcd5d 100644 --- a/src/common/Cargo.toml +++ b/src/common/Cargo.toml @@ -14,16 +14,21 @@ ignored = ["workspace-hack"] normal = ["workspace-hack"] [dependencies] +ahash = "0.8" anyhow = "1" arc-swap = "1" arrow-array = { workspace = true } arrow-array-deltalake = { workspace = true } +arrow-array-iceberg = { workspace = true } arrow-buffer = { workspace = true } arrow-buffer-deltalake = { workspace = true } +arrow-buffer-iceberg = { workspace = true } arrow-cast = { workspace = true } arrow-cast-deltalake = { workspace = true } +arrow-cast-iceberg = { workspace = true } arrow-schema = { workspace = true } arrow-schema-deltalake = { workspace = true } +arrow-schema-iceberg = { workspace = true } async-trait = "0.1" auto_enums = { workspace = true } auto_impl = "1" @@ -34,12 +39,12 @@ chrono = { version = "0.4", default-features = false, features = [ "clock", "std", ] } -chrono-tz = { version = "0.8", features = ["case-insensitive"] } +chrono-tz = { version = "0.9", features = ["case-insensitive"] } clap = { workspace = true } comfy-table = "7" crc32fast = "1" easy-ext = "1" -educe = "0.5" +educe = "0.6" either = "1" enum-as-inner = "0.6" enumflags2 = { version = "0.7.8" } @@ -48,13 +53,14 @@ fixedbitset = "0.5" foyer = { workspace = true } futures = { version = "0.3", default-features = false, features = ["alloc"] } governor = { version = "0.6", default-features = false, features = ["std"] } +hashbrown = "0.14" hex = "0.4.3" http = "0.2" humantime = "2.1" hytra = { workspace = true } itertools = { workspace = true } itoa = "1.0" -jsonbb = "0.1.2" +jsonbb = { workspace = true } lru = { workspace = true } memcomparable = { version = "0.2", features = ["decimal"] } num-integer = "0.1" @@ -81,6 +87,7 @@ risingwave_common_estimate_size = { workspace = true } risingwave_common_metrics = { path = "./metrics" } risingwave_common_proc_macro = { workspace = true } risingwave_error = { workspace = true } +risingwave_license = { workspace = true } risingwave_pb = { workspace = true } rust_decimal = { version = "1", features = ["db-postgres", "maths"] } rw_iter_util = { workspace = true } @@ -110,6 +117,7 @@ tokio = { version = "0.2", package = "madsim-tokio", features = [ "signal", ] } tokio-retry = "0.3" +tokio-util = { workspace = true } toml = "0.8" tracing = "0.1" tracing-futures = { version = "0.2", features = ["futures-03"] } @@ -135,6 +143,7 @@ libc = "0.2" mach2 = "0.4" [dev-dependencies] +coarsetime = "0.1" criterion = { workspace = true } expect-test = "1" more-asserts = "0.3" @@ -167,5 +176,13 @@ harness = false name = "bench_array" harness = false +[[bench]] +name = "bench_sequencer" +harness = false + +[[bench]] +name = "bench_lru" +harness = false + [lints] workspace = true diff --git a/src/common/benches/bench_lru.rs b/src/common/benches/bench_lru.rs new file mode 100644 index 0000000000000..84f49a405a106 --- /dev/null +++ b/src/common/benches/bench_lru.rs @@ -0,0 +1,88 @@ +// Copyright 2024 RisingWave Labs +// +// 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::hint::black_box; +use std::sync::atomic::Ordering; +use std::time::{Duration, Instant}; + +use itertools::Itertools; +use lru::LruCache; +use risingwave_common::lru::LruCache as RwLruCache; +use risingwave_common::sequence::SEQUENCE_GLOBAL; + +fn lru(loops: usize, evict_ratio: u64) -> (usize, Duration) { + let mut lru = LruCache::unbounded(); + let mut evicted = 0; + let now = Instant::now(); + for i in 0..loops as u64 { + if i % evict_ratio == 0 && i != 0 { + lru.update_epoch(i); + while lru.pop_lru_by_epoch(i).is_some() { + evicted += 1; + } + } + lru.put(i, i); + } + + (evicted, now.elapsed()) +} + +fn rw_lru(loops: usize, evict_ratio: u64) -> (usize, Duration) { + let mut lru = RwLruCache::unbounded(); + let mut evicted = 0; + let now = Instant::now(); + for i in 0..loops as u64 { + if i % evict_ratio == 0 { + let sequence = SEQUENCE_GLOBAL.load(Ordering::Relaxed); + while lru.pop_with_sequence(sequence).is_some() { + evicted += 1; + } + } + lru.put(i, i); + } + + (evicted, now.elapsed()) +} + +fn benchmark(name: &str, threads: usize, loops: usize, f: F) +where + F: Fn() -> (usize, Duration) + Clone + Send + 'static, +{ + let handles = (0..threads) + .map(|_| std::thread::spawn(black_box(f.clone()))) + .collect_vec(); + let mut dur = Duration::from_nanos(0); + let mut evicted = 0; + for handle in handles { + let (e, d) = handle.join().unwrap(); + evicted += e; + dur += d; + } + println!( + "{:20} {} threads {} loops: {:?} per iter, total evicted: {}", + name, + threads, + loops, + Duration::from_nanos((dur.as_nanos() / threads as u128 / loops as u128) as u64), + evicted, + ); +} + +fn main() { + for threads in [1, 4, 8, 16, 32, 64] { + println!(); + benchmark("lru - 1024", threads, 1000000, || lru(1000000, 1024)); + benchmark("rw - 1024", threads, 1000000, || rw_lru(1000000, 1024)); + } +} diff --git a/src/common/benches/bench_sequencer.rs b/src/common/benches/bench_sequencer.rs new file mode 100644 index 0000000000000..12e92f1f3332d --- /dev/null +++ b/src/common/benches/bench_sequencer.rs @@ -0,0 +1,170 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +#![feature(lint_reasons)] + +use std::cell::RefCell; +use std::hint::black_box; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use itertools::Itertools; +use risingwave_common::sequence::*; + +thread_local! { + pub static SEQUENCER_64_8: RefCell = const { RefCell::new(Sequencer::new(64, 64 * 8)) }; + pub static SEQUENCER_64_16: RefCell = const { RefCell::new(Sequencer::new(64, 64 * 16)) }; + pub static SEQUENCER_64_32: RefCell = const { RefCell::new(Sequencer::new(64, 64 * 32)) }; + pub static SEQUENCER_128_8: RefCell = const { RefCell::new(Sequencer::new(128, 128 * 8)) }; + pub static SEQUENCER_128_16: RefCell = const { RefCell::new(Sequencer::new(128, 128 * 16)) }; + pub static SEQUENCER_128_32: RefCell = const { RefCell::new(Sequencer::new(128, 128 * 32)) }; +} + +fn coarse(loops: usize) -> Duration { + let now = Instant::now(); + for _ in 0..loops { + let _ = coarsetime::Instant::now(); + } + now.elapsed() +} + +#[expect(clippy::explicit_counter_loop)] +fn primitive(loops: usize) -> Duration { + let mut cnt = 0usize; + let now = Instant::now(); + for _ in 0..loops { + cnt += 1; + let _ = cnt; + } + now.elapsed() +} + +fn atomic(loops: usize, atomic: Arc) -> Duration { + let now = Instant::now(); + for _ in 0..loops { + let _ = atomic.fetch_add(1, Ordering::Relaxed); + } + now.elapsed() +} + +fn atomic_skip(loops: usize, atomic: Arc, skip: usize) -> Duration { + let mut cnt = 0usize; + let now = Instant::now(); + for _ in 0..loops { + cnt += 1; + let _ = cnt; + if cnt % skip == 0 { + let _ = atomic.fetch_add(skip, Ordering::Relaxed); + } else { + let _ = atomic.load(Ordering::Relaxed); + } + } + now.elapsed() +} + +fn sequencer(loops: usize, step: Sequence, lag_amp: Sequence) -> Duration { + let sequencer = match (step, lag_amp) { + (64, 8) => &SEQUENCER_64_8, + (64, 16) => &SEQUENCER_64_16, + (64, 32) => &SEQUENCER_64_32, + (128, 8) => &SEQUENCER_128_8, + (128, 16) => &SEQUENCER_128_16, + (128, 32) => &SEQUENCER_128_32, + _ => unimplemented!(), + }; + let now = Instant::now(); + for _ in 0..loops { + let _ = sequencer.with(|s| s.borrow_mut().alloc()); + } + now.elapsed() +} + +fn benchmark(name: &str, threads: usize, loops: usize, f: F) +where + F: Fn() -> Duration + Clone + Send + 'static, +{ + let handles = (0..threads) + .map(|_| std::thread::spawn(black_box(f.clone()))) + .collect_vec(); + let mut dur = Duration::from_nanos(0); + for handle in handles { + dur += handle.join().unwrap(); + } + println!( + "{:20} {} threads {} loops: {:?} per iter", + name, + threads, + loops, + Duration::from_nanos((dur.as_nanos() / threads as u128 / loops as u128) as u64) + ); +} + +fn main() { + for (threads, loops) in [ + (1, 10_000_000), + (4, 10_000_000), + (8, 10_000_000), + (16, 10_000_000), + (32, 10_000_000), + ] { + println!(); + + benchmark("primitive", threads, loops, move || primitive(loops)); + + let a = Arc::new(AtomicUsize::new(0)); + benchmark("atomic", threads, loops, move || atomic(loops, a.clone())); + + let a = Arc::new(AtomicUsize::new(0)); + benchmark("atomic skip 8", threads, loops, move || { + atomic_skip(loops, a.clone(), 8) + }); + + let a = Arc::new(AtomicUsize::new(0)); + benchmark("atomic skip 16", threads, loops, move || { + atomic_skip(loops, a.clone(), 16) + }); + + let a = Arc::new(AtomicUsize::new(0)); + benchmark("atomic skip 32", threads, loops, move || { + atomic_skip(loops, a.clone(), 32) + }); + + let a = Arc::new(AtomicUsize::new(0)); + benchmark("atomic skip 64", threads, loops, move || { + atomic_skip(loops, a.clone(), 64) + }); + + benchmark("sequencer(64,8)", threads, loops, move || { + sequencer(loops, 64, 8) + }); + benchmark("sequencer(64,16)", threads, loops, move || { + sequencer(loops, 64, 16) + }); + benchmark("sequencer(64,32)", threads, loops, move || { + sequencer(loops, 64, 32) + }); + benchmark("sequencer(128,8)", threads, loops, move || { + sequencer(loops, 128, 8) + }); + benchmark("sequencer(128,16)", threads, loops, move || { + sequencer(loops, 128, 16) + }); + benchmark("sequencer(128,32)", threads, loops, move || { + sequencer(loops, 128, 32) + }); + + benchmark("coarse", threads, loops, move || coarse(loops)); + } +} diff --git a/src/common/benches/bitmap.rs b/src/common/benches/bitmap.rs index 97a1e2b6d837b..e064613d683b4 100644 --- a/src/common/benches/bitmap.rs +++ b/src/common/benches/bitmap.rs @@ -14,7 +14,7 @@ use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use itertools::Itertools; -use risingwave_common::buffer::{Bitmap, BitmapIter}; +use risingwave_common::bitmap::{Bitmap, BitmapIter}; fn bench_bitmap(c: &mut Criterion) { const CHUNK_SIZE: usize = 1024; diff --git a/src/common/common_service/src/observer_manager.rs b/src/common/common_service/src/observer_manager.rs index 3f18be7697520..e760a0e16866c 100644 --- a/src/common/common_service/src/observer_manager.rs +++ b/src/common/common_service/src/observer_manager.rs @@ -27,6 +27,7 @@ pub trait SubscribeTypeEnum { } pub struct SubscribeFrontend {} + impl SubscribeTypeEnum for SubscribeFrontend { fn subscribe_type() -> SubscribeType { SubscribeType::Frontend @@ -34,6 +35,7 @@ impl SubscribeTypeEnum for SubscribeFrontend { } pub struct SubscribeHummock {} + impl SubscribeTypeEnum for SubscribeHummock { fn subscribe_type() -> SubscribeType { SubscribeType::Hummock @@ -41,6 +43,7 @@ impl SubscribeTypeEnum for SubscribeHummock { } pub struct SubscribeCompactor {} + impl SubscribeTypeEnum for SubscribeCompactor { fn subscribe_type() -> SubscribeType { SubscribeType::Compactor @@ -48,6 +51,7 @@ impl SubscribeTypeEnum for SubscribeCompactor { } pub struct SubscribeCompute {} + impl SubscribeTypeEnum for SubscribeCompute { fn subscribe_type() -> SubscribeType { SubscribeType::Compute @@ -142,12 +146,10 @@ where | Info::RelationGroup(_) | Info::User(_) | Info::Connection(_) + | Info::Secret(_) | Info::Function(_) => { notification.version > info.version.as_ref().unwrap().catalog_version } - Info::ParallelUnitMapping(_) => { - notification.version > info.version.as_ref().unwrap().parallel_unit_mapping_version - } Info::Node(_) => { notification.version > info.version.as_ref().unwrap().worker_node_version } @@ -157,10 +159,18 @@ where Info::HummockSnapshot(_) => true, Info::MetaBackupManifestId(_) => true, Info::SystemParams(_) | Info::SessionParam(_) => true, - Info::ServingParallelUnitMappings(_) => true, Info::Snapshot(_) | Info::HummockWriteLimits(_) => unreachable!(), Info::HummockStats(_) => true, Info::Recovery(_) => true, + Info::StreamingWorkerSlotMapping(_) => { + notification.version + > info + .version + .as_ref() + .unwrap() + .streaming_worker_slot_mapping_version + } + Info::ServingWorkerSlotMappings(_) => true, }); self.observer_states @@ -226,6 +236,7 @@ where } } } + const RE_SUBSCRIBE_RETRY_INTERVAL: Duration = Duration::from_millis(100); #[async_trait::async_trait] diff --git a/src/common/estimate_size/Cargo.toml b/src/common/estimate_size/Cargo.toml index 04bcf369cc588..77e4203f9c7cb 100644 --- a/src/common/estimate_size/Cargo.toml +++ b/src/common/estimate_size/Cargo.toml @@ -16,10 +16,10 @@ normal = ["workspace-hack"] [dependencies] bytes = "1" -educe = "0.5" +educe = "0.6" ethnum = { version = "1", features = ["serde"] } fixedbitset = "0.5" -jsonbb = "0.1.2" +jsonbb = { workspace = true } lru = { workspace = true } risingwave_common_proc_macro = { workspace = true } rust_decimal = "1" diff --git a/src/common/estimate_size/src/collections/btreemap.rs b/src/common/estimate_size/src/collections/btreemap.rs index cf4398eb191df..84ecd687d8df1 100644 --- a/src/common/estimate_size/src/collections/btreemap.rs +++ b/src/common/estimate_size/src/collections/btreemap.rs @@ -92,7 +92,11 @@ where // [ left, [mid], right ] let mut mid_right = self.inner.split_off(start); - let mid_right_split_key = mid_right.lower_bound(Bound::Excluded(end)).key().cloned(); + let mid_right_split_key = mid_right + .lower_bound(Bound::Excluded(end)) + .peek_next() + .map(|(k, _)| k) + .cloned(); let right = if let Some(ref mid_right_split_key) = mid_right_split_key { mid_right.split_off(mid_right_split_key) } else { diff --git a/src/common/estimate_size/src/collections/lru.rs b/src/common/estimate_size/src/collections/lru.rs deleted file mode 100644 index 4add8df754673..0000000000000 --- a/src/common/estimate_size/src/collections/lru.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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::alloc::{Allocator, Global}; -use std::borrow::Borrow; -use std::hash::{BuildHasher, Hash}; - -use lru::{DefaultHasher, LruCache}; - -use super::{AtomicMutGuard, MutGuard}; -use crate::{EstimateSize, KvSize}; - -/// The managed cache is a lru cache that bounds the memory usage by epoch. -/// Should be used with `MemoryManager`. -pub struct EstimatedLruCache { - inner: LruCache, - kv_heap_size: KvSize, -} - -impl - EstimatedLruCache -{ - pub fn with_hasher_in(hasher: S, alloc: A) -> Self { - Self { - inner: LruCache::unbounded_with_hasher_in(hasher, alloc), - kv_heap_size: KvSize::new(), - } - } - - /// Evict epochs lower than the watermark - pub fn evict_by_epoch(&mut self, epoch: u64) { - while let Some((key, value, _)) = self.inner.pop_lru_by_epoch(epoch) { - self.kv_heap_size.sub(&key, &value); - } - } - - pub fn update_epoch(&mut self, epoch: u64) { - self.inner.update_epoch(epoch); - } - - pub fn current_epoch(&mut self) -> u64 { - self.inner.current_epoch() - } - - /// An iterator visiting all values in most-recently used order. The iterator element type is - /// &V. - pub fn values(&self) -> impl Iterator { - self.inner.iter().map(|(_k, v)| v) - } - - pub fn get_mut(&mut self, k: &K) -> Option> { - let v = self.inner.get_mut(k); - v.map(|inner| MutGuard::new(inner, &mut self.kv_heap_size)) - } - - pub fn get(&mut self, k: &Q) -> Option<&V> - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - self.inner.get(k) - } - - pub fn iter_mut( - &mut self, - ) -> impl ExactSizeIterator)> + '_ { - let kv_heap_size = &self.kv_heap_size; - self.inner.iter_mut().map(move |(k, v)| { - let guard = AtomicMutGuard::new(v, kv_heap_size); - (k, guard) - }) - } - - pub fn peek_mut(&mut self, k: &K) -> Option> { - let v = self.inner.peek_mut(k); - v.map(|inner| MutGuard::new(inner, &mut self.kv_heap_size)) - } - - pub fn push(&mut self, k: K, v: V) -> Option<(K, V)> { - self.kv_heap_size.add(&k, &v); - - let old_kv = self.inner.push(k, v); - - if let Some((old_key, old_val)) = &old_kv { - self.kv_heap_size.sub(old_key, old_val); - } - old_kv - } - - pub fn contains(&self, k: &Q) -> bool - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - self.inner.contains(k) - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.len() == 0 - } - - pub fn clear(&mut self) { - self.inner.clear(); - } -} - -impl EstimatedLruCache { - pub fn unbounded() -> Self { - Self { - inner: LruCache::unbounded(), - kv_heap_size: KvSize::new(), - } - } -} - -impl EstimatedLruCache { - pub fn unbounded_with_hasher(hasher: S) -> Self { - Self { - inner: LruCache::unbounded_with_hasher(hasher), - kv_heap_size: KvSize::new(), - } - } -} - -impl - EstimatedLruCache -{ - pub fn unbounded_with_hasher_in(hasher: S, allocator: A) -> Self { - Self { - inner: LruCache::unbounded_with_hasher_in(hasher, allocator), - kv_heap_size: KvSize::new(), - } - } -} - -impl - EstimateSize for EstimatedLruCache -{ - fn estimated_heap_size(&self) -> usize { - // TODO: Add lru cache internal size - // https://github.com/risingwavelabs/risingwave/issues/9713 - self.kv_heap_size.size() - } -} diff --git a/src/common/estimate_size/src/collections/mod.rs b/src/common/estimate_size/src/collections/mod.rs index f7cdff490a88d..5bffd5133eddc 100644 --- a/src/common/estimate_size/src/collections/mod.rs +++ b/src/common/estimate_size/src/collections/mod.rs @@ -16,7 +16,6 @@ use std::ops::{Deref, DerefMut}; use super::{EstimateSize, KvSize}; -pub mod lru; pub mod vecdeque; pub use vecdeque::EstimatedVecDeque; pub mod hashmap; diff --git a/src/common/fields-derive/Cargo.toml b/src/common/fields-derive/Cargo.toml index 5bd03de647183..91806d851ae07 100644 --- a/src/common/fields-derive/Cargo.toml +++ b/src/common/fields-derive/Cargo.toml @@ -18,4 +18,4 @@ syn = { version = "2", features = ["full", "extra-traits"] } [dev-dependencies] expect-test = "1" indoc = "2" -prettyplease = "0.2" +prettyplease = { version = "0.2", features = ["verbatim"] } diff --git a/src/common/fields-derive/src/gen/test_empty_pk.rs b/src/common/fields-derive/src/gen/test_empty_pk.rs index ffb5ff268bed1..9188343ca2146 100644 --- a/src/common/fields-derive/src/gen/test_empty_pk.rs +++ b/src/common/fields-derive/src/gen/test_empty_pk.rs @@ -2,16 +2,21 @@ impl ::risingwave_common::types::Fields for Data { const PRIMARY_KEY: Option<&'static [usize]> = Some(&[]); fn fields() -> Vec<(&'static str, ::risingwave_common::types::DataType)> { vec![ - ("v1", < i16 as ::risingwave_common::types::WithDataType > - ::default_data_type()), ("v2", < String as - ::risingwave_common::types::WithDataType > ::default_data_type()) + ( + "v1", + ::default_data_type(), + ), + ( + "v2", + ::default_data_type(), + ), ] } fn into_owned_row(self) -> ::risingwave_common::row::OwnedRow { ::risingwave_common::row::OwnedRow::new( vec![ ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v1), - ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v2) + ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v2), ], ) } @@ -21,7 +26,7 @@ impl From for ::risingwave_common::types::ScalarImpl { ::risingwave_common::types::StructValue::new( vec![ ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v1), - ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v2) + ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v2), ], ) .into() diff --git a/src/common/fields-derive/src/gen/test_no_pk.rs b/src/common/fields-derive/src/gen/test_no_pk.rs index 9e1b3e7892969..710c6717edc42 100644 --- a/src/common/fields-derive/src/gen/test_no_pk.rs +++ b/src/common/fields-derive/src/gen/test_no_pk.rs @@ -2,16 +2,21 @@ impl ::risingwave_common::types::Fields for Data { const PRIMARY_KEY: Option<&'static [usize]> = None; fn fields() -> Vec<(&'static str, ::risingwave_common::types::DataType)> { vec![ - ("v1", < i16 as ::risingwave_common::types::WithDataType > - ::default_data_type()), ("v2", < String as - ::risingwave_common::types::WithDataType > ::default_data_type()) + ( + "v1", + ::default_data_type(), + ), + ( + "v2", + ::default_data_type(), + ), ] } fn into_owned_row(self) -> ::risingwave_common::row::OwnedRow { ::risingwave_common::row::OwnedRow::new( vec![ ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v1), - ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v2) + ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v2), ], ) } @@ -21,7 +26,7 @@ impl From for ::risingwave_common::types::ScalarImpl { ::risingwave_common::types::StructValue::new( vec![ ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v1), - ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v2) + ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v2), ], ) .into() diff --git a/src/common/fields-derive/src/gen/test_output.rs b/src/common/fields-derive/src/gen/test_output.rs index a804a379bfd4a..314248dbfda76 100644 --- a/src/common/fields-derive/src/gen/test_output.rs +++ b/src/common/fields-derive/src/gen/test_output.rs @@ -2,13 +2,26 @@ impl ::risingwave_common::types::Fields for Data { const PRIMARY_KEY: Option<&'static [usize]> = Some(&[1usize, 0usize]); fn fields() -> Vec<(&'static str, ::risingwave_common::types::DataType)> { vec![ - ("v1", < i16 as ::risingwave_common::types::WithDataType > - ::default_data_type()), ("v2", < std::primitive::i32 as - ::risingwave_common::types::WithDataType > ::default_data_type()), ("v3", < - bool as ::risingwave_common::types::WithDataType > ::default_data_type()), - ("v4", < Serial as ::risingwave_common::types::WithDataType > - ::default_data_type()), ("type", < i32 as - ::risingwave_common::types::WithDataType > ::default_data_type()) + ( + "v1", + ::default_data_type(), + ), + ( + "v2", + ::default_data_type(), + ), + ( + "v3", + ::default_data_type(), + ), + ( + "v4", + ::default_data_type(), + ), + ( + "type", + ::default_data_type(), + ), ] } fn into_owned_row(self) -> ::risingwave_common::row::OwnedRow { @@ -18,7 +31,7 @@ impl ::risingwave_common::types::Fields for Data { ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v2), ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v3), ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.v4), - ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.r#type) + ::risingwave_common::types::ToOwnedDatum::to_owned_datum(self.r#type), ], ) } @@ -31,7 +44,7 @@ impl From for ::risingwave_common::types::ScalarImpl { ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v2), ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v3), ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.v4), - ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.r#type) + ::risingwave_common::types::ToOwnedDatum::to_owned_datum(v.r#type), ], ) .into() diff --git a/src/common/heap_profiling/src/jeprof.rs b/src/common/heap_profiling/src/jeprof.rs index b0ae0f1658c9e..6c17fed7045a5 100644 --- a/src/common/heap_profiling/src/jeprof.rs +++ b/src/common/heap_profiling/src/jeprof.rs @@ -14,7 +14,6 @@ use std::path::Path; use std::process::Command; -use std::result::Result; use std::{env, fs}; /// Error type for running `jeprof`. diff --git a/src/common/metrics/src/guarded_metrics.rs b/src/common/metrics/src/guarded_metrics.rs index 9c0919d7a420b..27710748ae359 100644 --- a/src/common/metrics/src/guarded_metrics.rs +++ b/src/common/metrics/src/guarded_metrics.rs @@ -21,31 +21,15 @@ use std::sync::Arc; use itertools::Itertools; use parking_lot::Mutex; use prometheus::core::{ - Atomic, AtomicF64, AtomicI64, AtomicU64, Collector, Desc, GenericCounter, GenericCounterVec, - GenericGauge, GenericGaugeVec, GenericLocalCounter, MetricVec, MetricVecBuilder, + Atomic, AtomicF64, AtomicI64, AtomicU64, Collector, Desc, GenericCounter, GenericLocalCounter, + MetricVec, MetricVecBuilder, }; use prometheus::local::{LocalHistogram, LocalIntCounter}; use prometheus::proto::MetricFamily; -use prometheus::{Gauge, Histogram, HistogramVec, IntCounter, IntGauge}; +use prometheus::{Gauge, Histogram, IntCounter, IntGauge}; use thiserror_ext::AsReport; use tracing::warn; -pub fn __extract_counter_builder( - vec: GenericCounterVec

, -) -> MetricVec> { - vec -} - -pub fn __extract_gauge_builder( - vec: GenericGaugeVec

, -) -> MetricVec> { - vec -} - -pub fn __extract_histogram_builder(vec: HistogramVec) -> MetricVec { - vec -} - #[macro_export] macro_rules! register_guarded_histogram_vec_with_registry { ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ @@ -55,6 +39,13 @@ macro_rules! register_guarded_histogram_vec_with_registry { $REGISTRY } }}; + ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $BUCKETS:expr, $REGISTRY:expr $(,)?) => {{ + $crate::register_guarded_histogram_vec_with_registry! { + {prometheus::histogram_opts!($NAME, $HELP, $BUCKETS)}, + $LABELS_NAMES, + $REGISTRY + } + }}; ($HOPTS:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ let inner = prometheus::HistogramVec::new($HOPTS, $LABELS_NAMES); inner.and_then(|inner| { @@ -105,9 +96,35 @@ macro_rules! register_guarded_int_counter_vec_with_registry { }}; } -pub type VecBuilderOfCounter = impl MetricVecBuilder>; -pub type VecBuilderOfGauge = impl MetricVecBuilder>; -pub type VecBuilderOfHistogram = impl MetricVecBuilder; +// put TAITs in a separate module to avoid "non-defining opaque type use in defining scope" +mod tait { + use prometheus::core::{ + Atomic, GenericCounter, GenericCounterVec, GenericGauge, GenericGaugeVec, MetricVec, + MetricVecBuilder, + }; + use prometheus::{Histogram, HistogramVec}; + + pub type VecBuilderOfCounter = impl MetricVecBuilder>; + pub type VecBuilderOfGauge = impl MetricVecBuilder>; + pub type VecBuilderOfHistogram = impl MetricVecBuilder; + + pub fn __extract_counter_builder( + vec: GenericCounterVec

, + ) -> MetricVec> { + vec + } + + pub fn __extract_gauge_builder( + vec: GenericGaugeVec

, + ) -> MetricVec> { + vec + } + + pub fn __extract_histogram_builder(vec: HistogramVec) -> MetricVec { + vec + } +} +pub use tait::*; pub type LabelGuardedHistogramVec = LabelGuardedMetricVec; pub type LabelGuardedIntCounterVec = @@ -320,7 +337,7 @@ pub struct LabelGuardedMetric { _guard: Arc>, } -impl Debug for LabelGuardedMetric { +impl Debug for LabelGuardedMetric { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("LabelGuardedMetric").finish() } diff --git a/src/common/metrics/src/lib.rs b/src/common/metrics/src/lib.rs index a2e4156b525d1..fa0250f4f0c7b 100644 --- a/src/common/metrics/src/lib.rs +++ b/src/common/metrics/src/lib.rs @@ -15,7 +15,6 @@ #![feature(lazy_cell)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] -#![feature(array_methods)] use std::ops::Deref; use std::sync::LazyLock; @@ -29,11 +28,13 @@ use tracing_subscriber::Layer; mod error_metrics; mod guarded_metrics; +mod metrics; pub mod monitor; mod relabeled_metric; pub use error_metrics::*; pub use guarded_metrics::*; +pub use metrics::*; pub use relabeled_metric::*; #[derive(Debug)] diff --git a/src/common/metrics/src/metrics.rs b/src/common/metrics/src/metrics.rs new file mode 100644 index 0000000000000..80df71e7309b3 --- /dev/null +++ b/src/common/metrics/src/metrics.rs @@ -0,0 +1,43 @@ +// Copyright 2024 RisingWave Labs +// +// 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 prometheus::core::{AtomicU64, GenericGaugeVec}; + +pub type UintGaugeVec = GenericGaugeVec; + +#[macro_export] +macro_rules! register_gauge_vec { + ($TYPE:ident, $OPTS:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ + let gauge_vec = $TYPE::new($OPTS, $LABELS_NAMES).unwrap(); + $REGISTRY + .register(Box::new(gauge_vec.clone())) + .map(|_| gauge_vec) + }}; +} + +#[macro_export] +macro_rules! register_uint_gauge_vec_with_registry { + ($OPTS:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ + use $crate::UintGaugeVec; + $crate::register_gauge_vec!(UintGaugeVec, $OPTS, $LABELS_NAMES, $REGISTRY) + }}; + + ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ + register_uint_gauge_vec_with_registry!( + prometheus::opts!($NAME, $HELP), + $LABELS_NAMES, + $REGISTRY + ) + }}; +} diff --git a/src/common/metrics/src/monitor/connection.rs b/src/common/metrics/src/monitor/connection.rs index f5570ff97f60f..295fb6399ba4b 100644 --- a/src/common/metrics/src/monitor/connection.rs +++ b/src/common/metrics/src/monitor/connection.rs @@ -744,7 +744,7 @@ impl MonitorAsyncReadWrite for MonitorAsyncReadWriteImpl { } fn on_read_err(&mut self, err: &Error) { - // No need to store the value returned from with_label_values + // No need to store the value returned from `with_guarded_label_values` // because it is reporting a single error. GLOBAL_CONNECTION_METRICS .io_err_rate @@ -775,7 +775,7 @@ impl MonitorAsyncReadWrite for MonitorAsyncReadWriteImpl { } fn on_write_err(&mut self, err: &Error) { - // No need to store the value returned from with_label_values + // No need to store the value returned from `with_guarded_label_values` // because it is reporting a single error. GLOBAL_CONNECTION_METRICS .io_err_rate diff --git a/src/common/metrics/src/monitor/rwlock.rs b/src/common/metrics/src/monitor/rwlock.rs index 46f9d5edea91c..4d65c53801106 100644 --- a/src/common/metrics/src/monitor/rwlock.rs +++ b/src/common/metrics/src/monitor/rwlock.rs @@ -16,31 +16,34 @@ use prometheus::HistogramVec; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; pub struct MonitoredRwLock { + // labels: [lock_name, lock_type] metrics: HistogramVec, inner: RwLock, + lock_name: &'static str, } impl MonitoredRwLock { - pub fn new(metrics: HistogramVec, val: T) -> Self { + pub fn new(metrics: HistogramVec, val: T, lock_name: &'static str) -> Self { Self { metrics, inner: RwLock::new(val), + lock_name, } } - pub async fn read<'a, 'b>( - &'a self, - label_values: &'b [&'static str], - ) -> RwLockReadGuard<'a, T> { - let _timer = self.metrics.with_label_values(label_values).start_timer(); + pub async fn read(&self) -> RwLockReadGuard<'_, T> { + let _timer = self + .metrics + .with_label_values(&[self.lock_name, "read"]) + .start_timer(); self.inner.read().await } - pub async fn write<'a, 'b>( - &'a self, - label_values: &'b [&'static str], - ) -> RwLockWriteGuard<'a, T> { - let _timer = self.metrics.with_label_values(label_values).start_timer(); + pub async fn write(&self) -> RwLockWriteGuard<'_, T> { + let _timer = self + .metrics + .with_label_values(&[self.lock_name, "write"]) + .start_timer(); self.inner.write().await } } diff --git a/src/common/metrics/src/relabeled_metric.rs b/src/common/metrics/src/relabeled_metric.rs index 1b4b4e8aaa038..8c0582f15f875 100644 --- a/src/common/metrics/src/relabeled_metric.rs +++ b/src/common/metrics/src/relabeled_metric.rs @@ -16,16 +16,17 @@ use prometheus::core::{MetricVec, MetricVecBuilder}; use prometheus::{HistogramVec, IntCounterVec}; use crate::{ - LabelGuardedHistogramVec, LabelGuardedIntCounterVec, LabelGuardedMetric, LabelGuardedMetricVec, - MetricLevel, + LabelGuardedHistogramVec, LabelGuardedIntCounterVec, LabelGuardedIntGaugeVec, + LabelGuardedMetric, LabelGuardedMetricVec, MetricLevel, }; /// For all `Relabeled*Vec` below, /// - when `metric_level` <= `relabel_threshold`, they behaves exactly the same as their inner /// metric. /// - when `metric_level` > `relabel_threshold`, all their input label values are rewrite to "" when -/// calling `with_label_values`. That's means the metric vec is aggregated into a single metric. - +/// calling `with_label_values`. That's means the metric vec is aggregated into a single metric. +/// +/// /// These wrapper classes add a `metric_level` field to corresponding metric. /// We could have use one single struct to represent all `MetricVec`, rather /// than specializing them one by one. However, that's undoable because prometheus crate doesn't @@ -108,3 +109,5 @@ pub type RelabeledGuardedHistogramVec = RelabeledMetricVec>; pub type RelabeledGuardedIntCounterVec = RelabeledMetricVec>; +pub type RelabeledGuardedIntGaugeVec = + RelabeledMetricVec>; diff --git a/src/common/src/acl/mod.rs b/src/common/src/acl/mod.rs index 91f69fea51310..b1c41bc8ec6ad 100644 --- a/src/common/src/acl/mod.rs +++ b/src/common/src/acl/mod.rs @@ -14,7 +14,6 @@ //! `Acl` defines all grantable privileges. -use std::convert::Into; use std::fmt::Formatter; use std::sync::LazyLock; @@ -100,11 +99,11 @@ pub static ALL_AVAILABLE_DATABASE_MODES: LazyLock = LazyLock::new(|| make_bitflags!(AclMode::{Create | Connect}).into()); pub static ALL_AVAILABLE_SCHEMA_MODES: LazyLock = LazyLock::new(|| make_bitflags!(AclMode::{Create | Usage}).into()); +// Including TABLES and VIEWS pub static ALL_AVAILABLE_TABLE_MODES: LazyLock = LazyLock::new(|| make_bitflags!(AclMode::{Select | Insert | Update | Delete}).into()); pub static ALL_AVAILABLE_SOURCE_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); pub static ALL_AVAILABLE_MVIEW_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); -pub static ALL_AVAILABLE_VIEW_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); pub static ALL_AVAILABLE_SINK_MODES: LazyLock = LazyLock::new(AclModeSet::empty); pub static ALL_AVAILABLE_SUBSCRIPTION_MODES: LazyLock = LazyLock::new(AclModeSet::empty); diff --git a/src/common/src/array/arrow/arrow_default.rs b/src/common/src/array/arrow/arrow_default.rs deleted file mode 100644 index 5d04527b354ba..0000000000000 --- a/src/common/src/array/arrow/arrow_default.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -//! This is for arrow dependency named `arrow-xxx` such as `arrow-array` in the cargo workspace. -//! -//! This should the default arrow version to be used in our system. -//! -//! The corresponding version of arrow is currently used by `udf` and `iceberg` sink. - -#![allow(unused_imports)] -pub use arrow_impl::{ - to_record_batch_with_schema, ToArrowArrayConvert, ToArrowArrayWithTypeConvert, - ToArrowTypeConvert, -}; -use {arrow_array, arrow_buffer, arrow_cast, arrow_schema}; - -#[expect(clippy::duplicate_mod)] -#[path = "./arrow_impl.rs"] -mod arrow_impl; diff --git a/src/common/src/array/arrow/arrow_deltalake.rs b/src/common/src/array/arrow/arrow_deltalake.rs index c55cae305b07f..7338532e082d6 100644 --- a/src/common/src/array/arrow/arrow_deltalake.rs +++ b/src/common/src/array/arrow/arrow_deltalake.rs @@ -21,25 +21,30 @@ use std::ops::{Div, Mul}; use std::sync::Arc; use arrow_array::ArrayRef; -use arrow_schema::DataType; -use itertools::Itertools; use num_traits::abs; use { arrow_array_deltalake as arrow_array, arrow_buffer_deltalake as arrow_buffer, arrow_cast_deltalake as arrow_cast, arrow_schema_deltalake as arrow_schema, }; +type ArrowIntervalType = i128; -use self::arrow_impl::ToArrowArrayWithTypeConvert; -use crate::array::arrow::arrow_deltalake::arrow_impl::FromIntoArrow; -use crate::array::{Array, ArrayError, ArrayImpl, DataChunk, Decimal, DecimalArray, ListArray}; -use crate::util::iter_util::ZipEqFast; +use self::arrow_impl::ToArrow; +use crate::array::{Array, ArrayError, DataChunk, Decimal, DecimalArray}; #[expect(clippy::duplicate_mod)] #[path = "./arrow_impl.rs"] mod arrow_impl; -struct DeltaLakeConvert; +pub struct DeltaLakeConvert; impl DeltaLakeConvert { + pub fn to_record_batch( + &self, + schema: arrow_schema::SchemaRef, + chunk: &DataChunk, + ) -> Result { + ToArrow::to_record_batch(self, schema, chunk) + } + fn decimal_to_i128(decimal: Decimal, precision: u8, max_scale: i8) -> Option { match decimal { crate::array::Decimal::Normalized(e) => { @@ -67,7 +72,7 @@ impl DeltaLakeConvert { } } -impl arrow_impl::ToArrowArrayWithTypeConvert for DeltaLakeConvert { +impl ToArrow for DeltaLakeConvert { fn decimal_to_arrow( &self, data_type: &arrow_schema::DataType, @@ -89,189 +94,6 @@ impl arrow_impl::ToArrowArrayWithTypeConvert for DeltaLakeConvert { .map_err(ArrayError::from_arrow)?; Ok(Arc::new(array) as ArrayRef) } - - #[inline] - fn list_to_arrow( - &self, - data_type: &arrow_schema::DataType, - array: &ListArray, - ) -> Result { - use arrow_array::builder::*; - fn build( - array: &ListArray, - a: &A, - builder: B, - mut append: F, - ) -> arrow_array::ListArray - where - A: Array, - B: arrow_array::builder::ArrayBuilder, - F: FnMut(&mut B, Option>), - { - let mut builder = ListBuilder::with_capacity(builder, a.len()); - for i in 0..array.len() { - for j in array.offsets[i]..array.offsets[i + 1] { - append(builder.values(), a.value_at(j as usize)); - } - builder.append(!array.is_null(i)); - } - builder.finish() - } - let inner_type = match data_type { - arrow_schema::DataType::List(inner) => inner.data_type(), - _ => return Err(ArrayError::to_arrow("Invalid list type")), - }; - let arr: arrow_array::ListArray = match &*array.value { - ArrayImpl::Int16(a) => build(array, a, Int16Builder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }), - ArrayImpl::Int32(a) => build(array, a, Int32Builder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }), - ArrayImpl::Int64(a) => build(array, a, Int64Builder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }), - - ArrayImpl::Float32(a) => { - build(array, a, Float32Builder::with_capacity(a.len()), |b, v| { - b.append_option(v.map(|f| f.0)) - }) - } - ArrayImpl::Float64(a) => { - build(array, a, Float64Builder::with_capacity(a.len()), |b, v| { - b.append_option(v.map(|f| f.0)) - }) - } - ArrayImpl::Utf8(a) => build( - array, - a, - StringBuilder::with_capacity(a.len(), a.data().len()), - |b, v| b.append_option(v), - ), - ArrayImpl::Int256(a) => build( - array, - a, - Decimal256Builder::with_capacity(a.len()).with_data_type( - arrow_schema::DataType::Decimal256(arrow_schema::DECIMAL256_MAX_PRECISION, 0), - ), - |b, v| b.append_option(v.map(Into::into)), - ), - ArrayImpl::Bool(a) => { - build(array, a, BooleanBuilder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }) - } - ArrayImpl::Decimal(a) => { - let (precision, max_scale) = match inner_type { - arrow_schema::DataType::Decimal128(precision, scale) => (*precision, *scale), - _ => return Err(ArrayError::to_arrow("Invalid decimal type")), - }; - build( - array, - a, - Decimal128Builder::with_capacity(a.len()) - .with_data_type(DataType::Decimal128(precision, max_scale)), - |b, v| { - let v = v.and_then(|v| { - DeltaLakeConvert::decimal_to_i128(v, precision, max_scale) - }); - b.append_option(v); - }, - ) - } - ArrayImpl::Interval(a) => build( - array, - a, - IntervalMonthDayNanoBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Date(a) => build(array, a, Date32Builder::with_capacity(a.len()), |b, v| { - b.append_option(v.map(|d| d.into_arrow())) - }), - ArrayImpl::Timestamp(a) => build( - array, - a, - TimestampMicrosecondBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Timestamptz(a) => build( - array, - a, - TimestampMicrosecondBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Time(a) => build( - array, - a, - Time64MicrosecondBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Jsonb(a) => build( - array, - a, - LargeStringBuilder::with_capacity(a.len(), a.len() * 16), - |b, v| b.append_option(v.map(|j| j.to_string())), - ), - ArrayImpl::Serial(_) => todo!("list of serial"), - ArrayImpl::Struct(a) => { - let values = Arc::new(arrow_array::StructArray::try_from(a)?); - arrow_array::ListArray::new( - Arc::new(arrow_schema::Field::new( - "item", - a.data_type().try_into()?, - true, - )), - arrow_buffer::OffsetBuffer::new(arrow_buffer::ScalarBuffer::from( - array - .offsets() - .iter() - .map(|o| *o as i32) - .collect::>(), - )), - values, - Some(array.null_bitmap().into()), - ) - } - ArrayImpl::List(_) => todo!("list of list"), - ArrayImpl::Bytea(a) => build( - array, - a, - BinaryBuilder::with_capacity(a.len(), a.data().len()), - |b, v| b.append_option(v), - ), - }; - Ok(Arc::new(arr)) - } -} - -/// Converts RisingWave array to Arrow array with the schema. -/// This function will try to convert the array if the type is not same with the schema. -pub fn to_deltalake_record_batch_with_schema( - schema: arrow_schema::SchemaRef, - chunk: &DataChunk, -) -> Result { - if !chunk.is_compacted() { - let c = chunk.clone(); - return to_deltalake_record_batch_with_schema(schema, &c.compact()); - } - let columns: Vec<_> = chunk - .columns() - .iter() - .zip_eq_fast(schema.fields().iter()) - .map(|(column, field)| { - let column: arrow_array::ArrayRef = - DeltaLakeConvert.to_arrow_with_type(field.data_type(), column)?; - if column.data_type() == field.data_type() { - Ok(column) - } else { - arrow_cast::cast(&column, field.data_type()).map_err(ArrayError::from_arrow) - } - }) - .try_collect::<_, _, ArrayError>()?; - - let opts = arrow_array::RecordBatchOptions::default().with_row_count(Some(chunk.capacity())); - arrow_array::RecordBatch::try_new_with_options(schema, columns, &opts) - .map_err(ArrayError::to_arrow) } #[cfg(test)] @@ -283,8 +105,9 @@ mod test { use arrow_schema::Field; use {arrow_array_deltalake as arrow_array, arrow_schema_deltalake as arrow_schema}; + use crate::array::arrow::arrow_deltalake::DeltaLakeConvert; use crate::array::{ArrayImpl, Decimal, DecimalArray, ListArray, ListValue}; - use crate::buffer::Bitmap; + use crate::bitmap::Bitmap; #[test] fn test_decimal_list_chunk() { @@ -304,13 +127,14 @@ mod test { arrow_schema::DataType::List(Arc::new(Field::new( "test", arrow_schema::DataType::Decimal128(10, 0), - false, + true, ))), false, )]); - let record_batch = - super::to_deltalake_record_batch_with_schema(Arc::new(schema), &chunk).unwrap(); + let record_batch = DeltaLakeConvert + .to_record_batch(Arc::new(schema), &chunk) + .unwrap(); let expect_array = Arc::new( arrow_array::Decimal128Array::from(vec![ None, diff --git a/src/common/src/array/arrow/arrow_iceberg.rs b/src/common/src/array/arrow/arrow_iceberg.rs index 2dd7900da5da1..ff23bc102ee6b 100644 --- a/src/common/src/array/arrow/arrow_iceberg.rs +++ b/src/common/src/array/arrow/arrow_iceberg.rs @@ -4,7 +4,7 @@ // 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 +// 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, @@ -15,25 +15,109 @@ use std::ops::{Div, Mul}; use std::sync::Arc; -use arrow_array::{ArrayRef, StructArray}; -use arrow_schema::DataType; -use itertools::Itertools; +use arrow_array_iceberg::{self as arrow_array, ArrayRef}; +use arrow_buffer_iceberg::IntervalMonthDayNano as ArrowIntervalType; use num_traits::abs; +use { + arrow_buffer_iceberg as arrow_buffer, arrow_cast_iceberg as arrow_cast, + arrow_schema_iceberg as arrow_schema, +}; -use super::{ToArrowArrayWithTypeConvert, ToArrowTypeConvert}; -use crate::array::{Array, ArrayError, DataChunk, DecimalArray}; -use crate::util::iter_util::ZipEqFast; +use crate::array::{Array, ArrayError, ArrayImpl, DataChunk, DataType, DecimalArray}; +use crate::types::{Interval, StructType}; -struct IcebergArrowConvert; +impl ArrowIntervalTypeTrait for ArrowIntervalType { + fn to_interval(self) -> Interval { + // XXX: the arrow-rs decoding is incorrect + // let (months, days, ns) = arrow_array::types::IntervalMonthDayNanoType::to_parts(value); + Interval::from_month_day_usec(self.months, self.days, self.nanoseconds / 1000) + } -impl ToArrowTypeConvert for IcebergArrowConvert { - #[inline] - fn decimal_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Decimal128(arrow_schema::DECIMAL128_MAX_PRECISION, 0) + fn from_interval(value: Interval) -> Self { + // XXX: the arrow-rs encoding is incorrect + // arrow_array::types::IntervalMonthDayNanoType::make_value( + // self.months(), + // self.days(), + // // TODO: this may overflow and we need `try_into` + // self.usecs() * 1000, + // ) + Self { + months: value.months(), + days: value.days(), + nanoseconds: value.usecs() * 1000, + } + } +} + +#[path = "./arrow_impl.rs"] +mod arrow_impl; + +use arrow_impl::{FromArrow, ToArrow}; + +use crate::array::arrow::ArrowIntervalTypeTrait; + +pub struct IcebergArrowConvert; + +impl IcebergArrowConvert { + pub fn to_record_batch( + &self, + schema: arrow_schema::SchemaRef, + chunk: &DataChunk, + ) -> Result { + ToArrow::to_record_batch(self, schema, chunk) + } + + pub fn chunk_from_record_batch( + &self, + batch: &arrow_array::RecordBatch, + ) -> Result { + FromArrow::from_record_batch(self, batch) + } + + pub fn to_arrow_field( + &self, + name: &str, + data_type: &DataType, + ) -> Result { + ToArrow::to_arrow_field(self, name, data_type) + } + + pub fn type_from_field(&self, field: &arrow_schema::Field) -> Result { + FromArrow::from_field(self, field) + } + + pub fn struct_from_fields( + &self, + fields: &arrow_schema::Fields, + ) -> Result { + FromArrow::from_fields(self, fields) + } + + pub fn to_arrow_array( + &self, + data_type: &arrow_schema::DataType, + array: &ArrayImpl, + ) -> Result { + ToArrow::to_array(self, data_type, array) + } + + pub fn array_from_arrow_array( + &self, + field: &arrow_schema::Field, + array: &arrow_array::ArrayRef, + ) -> Result { + FromArrow::from_array(self, field, array) } } -impl ToArrowArrayWithTypeConvert for IcebergArrowConvert { +impl ToArrow for IcebergArrowConvert { + #[inline] + fn decimal_type_to_arrow(&self, name: &str) -> arrow_schema::Field { + let data_type = + arrow_schema::DataType::Decimal128(arrow_schema::DECIMAL128_MAX_PRECISION, 0); + arrow_schema::Field::new(name, data_type, true) + } + fn decimal_to_arrow( &self, data_type: &arrow_schema::DataType, @@ -85,72 +169,17 @@ impl ToArrowArrayWithTypeConvert for IcebergArrowConvert { } } -/// Converts RisingWave array to Arrow array with the schema. -/// The behavior is specified for iceberg: -/// For different struct type, try to use fields in schema to cast. -pub fn to_iceberg_record_batch_with_schema( - schema: arrow_schema::SchemaRef, - chunk: &DataChunk, -) -> Result { - if !chunk.is_compacted() { - let c = chunk.clone(); - return to_iceberg_record_batch_with_schema(schema, &c.compact()); - } - let columns: Vec<_> = chunk - .columns() - .iter() - .zip_eq_fast(schema.fields().iter()) - .map(|(column, field)| { - let column: arrow_array::ArrayRef = - IcebergArrowConvert {}.to_arrow_with_type(field.data_type(), column)?; - if column.data_type() == field.data_type() { - Ok(column) - } else if let DataType::Struct(actual) = column.data_type() - && let DataType::Struct(expect) = field.data_type() - { - // Special case for iceberg - if actual.len() != expect.len() { - return Err(ArrayError::to_arrow(format!( - "Struct field count mismatch, expect {}, actual {}", - expect.len(), - actual.len() - ))); - } - let column = column - .as_any() - .downcast_ref::() - .unwrap() - .clone(); - let (_, struct_columns, nullable) = column.into_parts(); - Ok(Arc::new( - StructArray::try_new(expect.clone(), struct_columns, nullable) - .map_err(ArrayError::from_arrow)?, - ) as ArrayRef) - } else { - arrow_cast::cast(&column, field.data_type()).map_err(ArrayError::from_arrow) - } - }) - .try_collect::<_, _, ArrayError>()?; - - let opts = arrow_array::RecordBatchOptions::default().with_row_count(Some(chunk.capacity())); - arrow_array::RecordBatch::try_new_with_options(schema, columns, &opts) - .map_err(ArrayError::to_arrow) -} - -pub fn iceberg_to_arrow_type( - data_type: &crate::array::DataType, -) -> Result { - IcebergArrowConvert {}.to_arrow_type(data_type) -} +impl FromArrow for IcebergArrowConvert {} #[cfg(test)] mod test { use std::sync::Arc; - use arrow_array::ArrayRef; + use arrow_array_iceberg::{ArrayRef, Decimal128Array}; + use arrow_schema_iceberg::DataType; - use crate::array::arrow::arrow_iceberg::IcebergArrowConvert; - use crate::array::arrow::ToArrowArrayWithTypeConvert; + use super::arrow_impl::ToArrow; + use super::IcebergArrowConvert; use crate::array::{Decimal, DecimalArray}; #[test] @@ -163,10 +192,10 @@ mod test { Some(Decimal::Normalized("123.4".parse().unwrap())), Some(Decimal::Normalized("123.456".parse().unwrap())), ]); - let ty = arrow_schema::DataType::Decimal128(6, 3); + let ty = DataType::Decimal128(6, 3); let arrow_array = IcebergArrowConvert.decimal_to_arrow(&ty, &array).unwrap(); let expect_array = Arc::new( - arrow_array::Decimal128Array::from(vec![ + Decimal128Array::from(vec![ None, None, Some(999999), diff --git a/src/common/src/array/arrow/arrow_impl.rs b/src/common/src/array/arrow/arrow_impl.rs index e426caa306d55..35057f62f7740 100644 --- a/src/common/src/array/arrow/arrow_impl.rs +++ b/src/common/src/array/arrow/arrow_impl.rs @@ -36,220 +36,136 @@ //! mod arrow_impl; //! ``` +// Is this a bug? Why do we have these lints? +#![allow(unused_imports)] +#![allow(dead_code)] + use std::fmt::Write; -use std::sync::Arc; -use chrono::{NaiveDateTime, NaiveTime}; +use arrow_buffer::OffsetBuffer; +use chrono::{DateTime, NaiveDateTime, NaiveTime}; use itertools::Itertools; // This is important because we want to use the arrow version specified by the outer mod. -use super::{arrow_array, arrow_buffer, arrow_cast, arrow_schema}; +use super::{arrow_array, arrow_buffer, arrow_cast, arrow_schema, ArrowIntervalType}; // Other import should always use the absolute path. use crate::array::*; -use crate::buffer::Bitmap; use crate::types::*; use crate::util::iter_util::ZipEqFast; -/// Converts RisingWave array to Arrow array with the schema. -/// This function will try to convert the array if the type is not same with the schema. -#[allow(dead_code)] -pub fn to_record_batch_with_schema( - schema: arrow_schema::SchemaRef, - chunk: &DataChunk, -) -> Result { - if !chunk.is_compacted() { - let c = chunk.clone(); - return to_record_batch_with_schema(schema, &c.compact()); - } - let columns: Vec<_> = chunk - .columns() - .iter() - .zip_eq_fast(schema.fields().iter()) - .map(|(column, field)| { - let column: arrow_array::ArrayRef = column.as_ref().try_into()?; - if column.data_type() == field.data_type() { - Ok(column) - } else { - arrow_cast::cast(&column, field.data_type()).map_err(ArrayError::from_arrow) - } - }) - .try_collect::<_, _, ArrayError>()?; - - let opts = arrow_array::RecordBatchOptions::default().with_row_count(Some(chunk.capacity())); - arrow_array::RecordBatch::try_new_with_options(schema, columns, &opts) - .map_err(ArrayError::to_arrow) -} - -// Implement bi-directional `From` between `DataChunk` and `arrow_array::RecordBatch`. -impl TryFrom<&DataChunk> for arrow_array::RecordBatch { - type Error = ArrayError; - - fn try_from(chunk: &DataChunk) -> Result { +/// Defines how to convert RisingWave arrays to Arrow arrays. +pub trait ToArrow { + /// Converts RisingWave `DataChunk` to Arrow `RecordBatch` with specified schema. + /// + /// This function will try to convert the array if the type is not same with the schema. + fn to_record_batch( + &self, + schema: arrow_schema::SchemaRef, + chunk: &DataChunk, + ) -> Result { + // compact the chunk if it's not compacted if !chunk.is_compacted() { let c = chunk.clone(); - return Self::try_from(&c.compact()); + return self.to_record_batch(schema, &c.compact()); } + + // convert each column to arrow array let columns: Vec<_> = chunk .columns() .iter() - .map(|column| column.as_ref().try_into()) - .try_collect::<_, _, Self::Error>()?; + .zip_eq_fast(schema.fields().iter()) + .map(|(column, field)| self.to_array(field.data_type(), column)) + .try_collect()?; - let fields: Vec<_> = columns - .iter() - .map(|array: &Arc| { - let nullable = array.null_count() > 0; - let data_type = array.data_type().clone(); - arrow_schema::Field::new("", data_type, nullable) - }) - .collect(); - - let schema = Arc::new(arrow_schema::Schema::new(fields)); + // create record batch let opts = arrow_array::RecordBatchOptions::default().with_row_count(Some(chunk.capacity())); arrow_array::RecordBatch::try_new_with_options(schema, columns, &opts) .map_err(ArrayError::to_arrow) } -} - -impl TryFrom<&arrow_array::RecordBatch> for DataChunk { - type Error = ArrayError; - - fn try_from(batch: &arrow_array::RecordBatch) -> Result { - let mut columns = Vec::with_capacity(batch.num_columns()); - for array in batch.columns() { - let column = Arc::new(array.try_into()?); - columns.push(column); - } - Ok(DataChunk::new(columns, batch.num_rows())) - } -} -/// Provides the default conversion logic for RisingWave array to Arrow array with type info. -pub trait ToArrowArrayWithTypeConvert { - fn to_arrow_with_type( + /// Converts RisingWave array to Arrow array. + fn to_array( &self, data_type: &arrow_schema::DataType, array: &ArrayImpl, ) -> Result { - match array { - ArrayImpl::Int16(array) => self.int16_to_arrow(data_type, array), - ArrayImpl::Int32(array) => self.int32_to_arrow(data_type, array), - ArrayImpl::Int64(array) => self.int64_to_arrow(data_type, array), - ArrayImpl::Float32(array) => self.float32_to_arrow(data_type, array), - ArrayImpl::Float64(array) => self.float64_to_arrow(data_type, array), - ArrayImpl::Utf8(array) => self.utf8_to_arrow(data_type, array), - ArrayImpl::Bool(array) => self.bool_to_arrow(data_type, array), + let arrow_array = match array { + ArrayImpl::Bool(array) => self.bool_to_arrow(array), + ArrayImpl::Int16(array) => self.int16_to_arrow(array), + ArrayImpl::Int32(array) => self.int32_to_arrow(array), + ArrayImpl::Int64(array) => self.int64_to_arrow(array), + ArrayImpl::Int256(array) => self.int256_to_arrow(array), + ArrayImpl::Float32(array) => self.float32_to_arrow(array), + ArrayImpl::Float64(array) => self.float64_to_arrow(array), + ArrayImpl::Date(array) => self.date_to_arrow(array), + ArrayImpl::Time(array) => self.time_to_arrow(array), + ArrayImpl::Timestamp(array) => self.timestamp_to_arrow(array), + ArrayImpl::Timestamptz(array) => self.timestamptz_to_arrow(array), + ArrayImpl::Interval(array) => self.interval_to_arrow(array), + ArrayImpl::Utf8(array) => self.utf8_to_arrow(array), + ArrayImpl::Bytea(array) => self.bytea_to_arrow(array), ArrayImpl::Decimal(array) => self.decimal_to_arrow(data_type, array), - ArrayImpl::Int256(array) => self.int256_to_arrow(data_type, array), - ArrayImpl::Date(array) => self.date_to_arrow(data_type, array), - ArrayImpl::Timestamp(array) => self.timestamp_to_arrow(data_type, array), - ArrayImpl::Timestamptz(array) => self.timestamptz_to_arrow(data_type, array), - ArrayImpl::Time(array) => self.time_to_arrow(data_type, array), - ArrayImpl::Interval(array) => self.interval_to_arrow(data_type, array), - ArrayImpl::Struct(array) => self.struct_to_arrow(data_type, array), + ArrayImpl::Jsonb(array) => self.jsonb_to_arrow(array), + ArrayImpl::Serial(array) => self.serial_to_arrow(array), ArrayImpl::List(array) => self.list_to_arrow(data_type, array), - ArrayImpl::Bytea(array) => self.bytea_to_arrow(data_type, array), - ArrayImpl::Jsonb(array) => self.jsonb_to_arrow(data_type, array), - ArrayImpl::Serial(array) => self.serial_to_arrow(data_type, array), + ArrayImpl::Struct(array) => self.struct_to_arrow(data_type, array), + }?; + if arrow_array.data_type() != data_type { + arrow_cast::cast(&arrow_array, data_type).map_err(ArrayError::to_arrow) + } else { + Ok(arrow_array) } } #[inline] - fn int16_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &I16Array, - ) -> Result { + fn bool_to_arrow(&self, array: &BoolArray) -> Result { + Ok(Arc::new(arrow_array::BooleanArray::from(array))) + } + + #[inline] + fn int16_to_arrow(&self, array: &I16Array) -> Result { Ok(Arc::new(arrow_array::Int16Array::from(array))) } #[inline] - fn int32_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &I32Array, - ) -> Result { + fn int32_to_arrow(&self, array: &I32Array) -> Result { Ok(Arc::new(arrow_array::Int32Array::from(array))) } #[inline] - fn int64_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &I64Array, - ) -> Result { + fn int64_to_arrow(&self, array: &I64Array) -> Result { Ok(Arc::new(arrow_array::Int64Array::from(array))) } #[inline] - fn float32_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &F32Array, - ) -> Result { + fn float32_to_arrow(&self, array: &F32Array) -> Result { Ok(Arc::new(arrow_array::Float32Array::from(array))) } #[inline] - fn float64_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &F64Array, - ) -> Result { + fn float64_to_arrow(&self, array: &F64Array) -> Result { Ok(Arc::new(arrow_array::Float64Array::from(array))) } #[inline] - fn utf8_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &Utf8Array, - ) -> Result { + fn utf8_to_arrow(&self, array: &Utf8Array) -> Result { Ok(Arc::new(arrow_array::StringArray::from(array))) } #[inline] - fn bool_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &BoolArray, - ) -> Result { - Ok(Arc::new(arrow_array::BooleanArray::from(array))) - } - - // Decimal values are stored as ASCII text representation in a large binary array. - #[inline] - fn decimal_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &DecimalArray, - ) -> Result { - Ok(Arc::new(arrow_array::LargeBinaryArray::from(array))) - } - - #[inline] - fn int256_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &Int256Array, - ) -> Result { + fn int256_to_arrow(&self, array: &Int256Array) -> Result { Ok(Arc::new(arrow_array::Decimal256Array::from(array))) } #[inline] - fn date_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &DateArray, - ) -> Result { + fn date_to_arrow(&self, array: &DateArray) -> Result { Ok(Arc::new(arrow_array::Date32Array::from(array))) } #[inline] fn timestamp_to_arrow( &self, - _data_type: &arrow_schema::DataType, array: &TimestampArray, ) -> Result { Ok(Arc::new(arrow_array::TimestampMicrosecondArray::from( @@ -260,7 +176,6 @@ pub trait ToArrowArrayWithTypeConvert { #[inline] fn timestamptz_to_arrow( &self, - _data_type: &arrow_schema::DataType, array: &TimestamptzArray, ) -> Result { Ok(Arc::new( @@ -269,18 +184,13 @@ pub trait ToArrowArrayWithTypeConvert { } #[inline] - fn time_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &TimeArray, - ) -> Result { + fn time_to_arrow(&self, array: &TimeArray) -> Result { Ok(Arc::new(arrow_array::Time64MicrosecondArray::from(array))) } #[inline] fn interval_to_arrow( &self, - _data_type: &arrow_schema::DataType, array: &IntervalArray, ) -> Result { Ok(Arc::new(arrow_array::IntervalMonthDayNanoArray::from( @@ -289,478 +199,494 @@ pub trait ToArrowArrayWithTypeConvert { } #[inline] - fn struct_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &StructArray, - ) -> Result { - Ok(Arc::new(arrow_array::StructArray::try_from(array)?)) + fn bytea_to_arrow(&self, array: &BytesArray) -> Result { + Ok(Arc::new(arrow_array::BinaryArray::from(array))) } + // Decimal values are stored as ASCII text representation in a string array. #[inline] - fn list_to_arrow( + fn decimal_to_arrow( &self, _data_type: &arrow_schema::DataType, - array: &ListArray, + array: &DecimalArray, ) -> Result { - Ok(Arc::new(arrow_array::ListArray::try_from(array)?)) + Ok(Arc::new(arrow_array::StringArray::from(array))) } + // JSON values are stored as text representation in a string array. #[inline] - fn bytea_to_arrow( - &self, - _data_type: &arrow_schema::DataType, - array: &BytesArray, - ) -> Result { - Ok(Arc::new(arrow_array::BinaryArray::from(array))) + fn jsonb_to_arrow(&self, array: &JsonbArray) -> Result { + Ok(Arc::new(arrow_array::StringArray::from(array))) + } + + #[inline] + fn serial_to_arrow(&self, array: &SerialArray) -> Result { + Ok(Arc::new(arrow_array::Int64Array::from(array))) } - // JSON values are stored as text representation in a large string array. #[inline] - fn jsonb_to_arrow( + fn list_to_arrow( &self, - _data_type: &arrow_schema::DataType, - array: &JsonbArray, + data_type: &arrow_schema::DataType, + array: &ListArray, ) -> Result { - Ok(Arc::new(arrow_array::LargeStringArray::from(array))) + let arrow_schema::DataType::List(field) = data_type else { + return Err(ArrayError::to_arrow("Invalid list type")); + }; + let values = self.to_array(field.data_type(), array.values())?; + let offsets = OffsetBuffer::new(array.offsets().iter().map(|&o| o as i32).collect()); + let nulls = (!array.null_bitmap().all()).then(|| array.null_bitmap().into()); + Ok(Arc::new(arrow_array::ListArray::new( + field.clone(), + offsets, + values, + nulls, + ))) } #[inline] - fn serial_to_arrow( + fn struct_to_arrow( &self, - _data_type: &arrow_schema::DataType, - _array: &SerialArray, + data_type: &arrow_schema::DataType, + array: &StructArray, ) -> Result { - todo!("serial type is not supported to convert to arrow") + let arrow_schema::DataType::Struct(fields) = data_type else { + return Err(ArrayError::to_arrow("Invalid struct type")); + }; + Ok(Arc::new(arrow_array::StructArray::new( + fields.clone(), + array + .fields() + .zip_eq_fast(fields) + .map(|(arr, field)| self.to_array(field.data_type(), arr)) + .try_collect::<_, _, ArrayError>()?, + Some(array.null_bitmap().into()), + ))) } -} -/// Provides the default conversion logic for RisingWave array to Arrow array with type info. -pub trait ToArrowArrayConvert { - fn to_arrow(&self, array: &ArrayImpl) -> Result { - match array { - ArrayImpl::Int16(array) => self.int16_to_arrow(array), - ArrayImpl::Int32(array) => self.int32_to_arrow(array), - ArrayImpl::Int64(array) => self.int64_to_arrow(array), - ArrayImpl::Float32(array) => self.float32_to_arrow(array), - ArrayImpl::Float64(array) => self.float64_to_arrow(array), - ArrayImpl::Utf8(array) => self.utf8_to_arrow(array), - ArrayImpl::Bool(array) => self.bool_to_arrow(array), - ArrayImpl::Decimal(array) => self.decimal_to_arrow(array), - ArrayImpl::Int256(array) => self.int256_to_arrow(array), - ArrayImpl::Date(array) => self.date_to_arrow(array), - ArrayImpl::Timestamp(array) => self.timestamp_to_arrow(array), - ArrayImpl::Timestamptz(array) => self.timestamptz_to_arrow(array), - ArrayImpl::Time(array) => self.time_to_arrow(array), - ArrayImpl::Interval(array) => self.interval_to_arrow(array), - ArrayImpl::Struct(array) => self.struct_to_arrow(array), - ArrayImpl::List(array) => self.list_to_arrow(array), - ArrayImpl::Bytea(array) => self.bytea_to_arrow(array), - ArrayImpl::Jsonb(array) => self.jsonb_to_arrow(array), - ArrayImpl::Serial(array) => self.serial_to_arrow(array), - } + /// Convert RisingWave data type to Arrow data type. + /// + /// This function returns a `Field` instead of `DataType` because some may be converted to + /// extension types which require additional metadata in the field. + fn to_arrow_field( + &self, + name: &str, + value: &DataType, + ) -> Result { + let data_type = match value { + // using the inline function + DataType::Boolean => self.bool_type_to_arrow(), + DataType::Int16 => self.int16_type_to_arrow(), + DataType::Int32 => self.int32_type_to_arrow(), + DataType::Int64 => self.int64_type_to_arrow(), + DataType::Int256 => self.int256_type_to_arrow(), + DataType::Float32 => self.float32_type_to_arrow(), + DataType::Float64 => self.float64_type_to_arrow(), + DataType::Date => self.date_type_to_arrow(), + DataType::Time => self.time_type_to_arrow(), + DataType::Timestamp => self.timestamp_type_to_arrow(), + DataType::Timestamptz => self.timestamptz_type_to_arrow(), + DataType::Interval => self.interval_type_to_arrow(), + DataType::Varchar => self.varchar_type_to_arrow(), + DataType::Bytea => self.bytea_type_to_arrow(), + DataType::Serial => self.serial_type_to_arrow(), + DataType::Decimal => return Ok(self.decimal_type_to_arrow(name)), + DataType::Jsonb => return Ok(self.jsonb_type_to_arrow(name)), + DataType::Struct(fields) => self.struct_type_to_arrow(fields)?, + DataType::List(datatype) => self.list_type_to_arrow(datatype)?, + }; + Ok(arrow_schema::Field::new(name, data_type, true)) } #[inline] - fn int16_to_arrow(&self, array: &I16Array) -> Result { - Ok(Arc::new(arrow_array::Int16Array::from(array))) + fn bool_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Boolean } #[inline] - fn int32_to_arrow(&self, array: &I32Array) -> Result { - Ok(Arc::new(arrow_array::Int32Array::from(array))) + fn int16_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Int16 } #[inline] - fn int64_to_arrow(&self, array: &I64Array) -> Result { - Ok(Arc::new(arrow_array::Int64Array::from(array))) + fn int32_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Int32 } #[inline] - fn float32_to_arrow(&self, array: &F32Array) -> Result { - Ok(Arc::new(arrow_array::Float32Array::from(array))) + fn int64_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Int64 } #[inline] - fn float64_to_arrow(&self, array: &F64Array) -> Result { - Ok(Arc::new(arrow_array::Float64Array::from(array))) + fn int256_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Decimal256(arrow_schema::DECIMAL256_MAX_PRECISION, 0) } #[inline] - fn utf8_to_arrow(&self, array: &Utf8Array) -> Result { - Ok(Arc::new(arrow_array::StringArray::from(array))) + fn float32_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Float32 } #[inline] - fn bool_to_arrow(&self, array: &BoolArray) -> Result { - Ok(Arc::new(arrow_array::BooleanArray::from(array))) + fn float64_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Float64 } - // Decimal values are stored as ASCII text representation in a large binary array. #[inline] - fn decimal_to_arrow(&self, array: &DecimalArray) -> Result { - Ok(Arc::new(arrow_array::LargeBinaryArray::from(array))) + fn date_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Date32 } #[inline] - fn int256_to_arrow(&self, array: &Int256Array) -> Result { - Ok(Arc::new(arrow_array::Decimal256Array::from(array))) + fn time_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Time64(arrow_schema::TimeUnit::Microsecond) } #[inline] - fn date_to_arrow(&self, array: &DateArray) -> Result { - Ok(Arc::new(arrow_array::Date32Array::from(array))) + fn timestamp_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Timestamp(arrow_schema::TimeUnit::Microsecond, None) } #[inline] - fn timestamp_to_arrow( - &self, - array: &TimestampArray, - ) -> Result { - Ok(Arc::new(arrow_array::TimestampMicrosecondArray::from( - array, - ))) + fn timestamptz_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Timestamp( + arrow_schema::TimeUnit::Microsecond, + Some("+00:00".into()), + ) } #[inline] - fn timestamptz_to_arrow( - &self, - array: &TimestamptzArray, - ) -> Result { - Ok(Arc::new( - arrow_array::TimestampMicrosecondArray::from(array).with_timezone_utc(), - )) + fn interval_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Interval(arrow_schema::IntervalUnit::MonthDayNano) } #[inline] - fn time_to_arrow(&self, array: &TimeArray) -> Result { - Ok(Arc::new(arrow_array::Time64MicrosecondArray::from(array))) + fn varchar_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Utf8 } #[inline] - fn interval_to_arrow( - &self, - array: &IntervalArray, - ) -> Result { - Ok(Arc::new(arrow_array::IntervalMonthDayNanoArray::from( - array, - ))) + fn jsonb_type_to_arrow(&self, name: &str) -> arrow_schema::Field { + arrow_schema::Field::new(name, arrow_schema::DataType::Utf8, true) + .with_metadata([("ARROW:extension:name".into(), "arrowudf.json".into())].into()) } #[inline] - fn struct_to_arrow(&self, array: &StructArray) -> Result { - Ok(Arc::new(arrow_array::StructArray::try_from(array)?)) + fn bytea_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Binary } #[inline] - fn list_to_arrow(&self, array: &ListArray) -> Result { - Ok(Arc::new(arrow_array::ListArray::try_from(array)?)) + fn decimal_type_to_arrow(&self, name: &str) -> arrow_schema::Field { + arrow_schema::Field::new(name, arrow_schema::DataType::Utf8, true) + .with_metadata([("ARROW:extension:name".into(), "arrowudf.decimal".into())].into()) } #[inline] - fn bytea_to_arrow(&self, array: &BytesArray) -> Result { - Ok(Arc::new(arrow_array::BinaryArray::from(array))) + fn serial_type_to_arrow(&self) -> arrow_schema::DataType { + arrow_schema::DataType::Int64 } - // JSON values are stored as text representation in a large string array. #[inline] - fn jsonb_to_arrow(&self, array: &JsonbArray) -> Result { - Ok(Arc::new(arrow_array::LargeStringArray::from(array))) + fn list_type_to_arrow( + &self, + elem_type: &DataType, + ) -> Result { + Ok(arrow_schema::DataType::List(Arc::new( + self.to_arrow_field("item", elem_type)?, + ))) } #[inline] - fn serial_to_arrow(&self, _array: &SerialArray) -> Result { - todo!("serial type is not supported to convert to arrow") + fn struct_type_to_arrow( + &self, + fields: &StructType, + ) -> Result { + Ok(arrow_schema::DataType::Struct( + fields + .iter() + .map(|(name, ty)| self.to_arrow_field(name, ty)) + .try_collect::<_, _, ArrayError>()?, + )) } } -pub trait ToArrowTypeConvert { - fn to_arrow_type(&self, value: &DataType) -> Result { - match value { - // using the inline function - DataType::Boolean => Ok(self.bool_type_to_arrow()), - DataType::Int16 => Ok(self.int16_type_to_arrow()), - DataType::Int32 => Ok(self.int32_type_to_arrow()), - DataType::Int64 => Ok(self.int64_type_to_arrow()), - DataType::Int256 => Ok(self.int256_type_to_arrow()), - DataType::Float32 => Ok(self.float32_type_to_arrow()), - DataType::Float64 => Ok(self.float64_type_to_arrow()), - DataType::Date => Ok(self.date_type_to_arrow()), - DataType::Timestamp => Ok(self.timestamp_type_to_arrow()), - DataType::Timestamptz => Ok(self.timestamptz_type_to_arrow()), - DataType::Time => Ok(self.time_type_to_arrow()), - DataType::Interval => Ok(self.interval_type_to_arrow()), - DataType::Varchar => Ok(self.varchar_type_to_arrow()), - DataType::Jsonb => Ok(self.jsonb_type_to_arrow()), - DataType::Bytea => Ok(self.bytea_type_to_arrow()), - DataType::Decimal => Ok(self.decimal_type_to_arrow()), - DataType::Serial => Ok(self.serial_type_to_arrow()), - DataType::Struct(fields) => self.struct_type_to_arrow(fields), - DataType::List(datatype) => self.list_type_to_arrow(datatype), +/// Defines how to convert Arrow arrays to RisingWave arrays. +#[allow(clippy::wrong_self_convention)] +pub trait FromArrow { + /// Converts Arrow `RecordBatch` to RisingWave `DataChunk`. + fn from_record_batch(&self, batch: &arrow_array::RecordBatch) -> Result { + let mut columns = Vec::with_capacity(batch.num_columns()); + for (array, field) in batch.columns().iter().zip_eq_fast(batch.schema().fields()) { + let column = Arc::new(self.from_array(field, array)?); + columns.push(column); } + Ok(DataChunk::new(columns, batch.num_rows())) } - #[inline] - fn bool_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Boolean + /// Converts Arrow `Fields` to RisingWave `StructType`. + fn from_fields(&self, fields: &arrow_schema::Fields) -> Result { + Ok(StructType::new( + fields + .iter() + .map(|f| Ok((f.name().clone(), self.from_field(f)?))) + .try_collect::<_, _, ArrayError>()?, + )) } - #[inline] - fn int32_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Int32 - } + /// Converts Arrow `Field` to RisingWave `DataType`. + fn from_field(&self, field: &arrow_schema::Field) -> Result { + use arrow_schema::DataType::*; + use arrow_schema::IntervalUnit::*; + use arrow_schema::TimeUnit::*; - #[inline] - fn int64_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Int64 - } + // extension type + if let Some(type_name) = field.metadata().get("ARROW:extension:name") { + return self.from_extension_type(type_name, field.data_type()); + } - // generate function for each type for me using inline - #[inline] - fn int16_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Int16 + Ok(match field.data_type() { + Boolean => DataType::Boolean, + Int16 => DataType::Int16, + Int32 => DataType::Int32, + Int64 => DataType::Int64, + Float32 => DataType::Float32, + Float64 => DataType::Float64, + Decimal128(_, _) => DataType::Decimal, + Decimal256(_, _) => DataType::Int256, + Date32 => DataType::Date, + Time64(Microsecond) => DataType::Time, + Timestamp(Microsecond, None) => DataType::Timestamp, + Timestamp(Microsecond, Some(_)) => DataType::Timestamptz, + Interval(MonthDayNano) => DataType::Interval, + Utf8 => DataType::Varchar, + Binary => DataType::Bytea, + LargeUtf8 => self.from_large_utf8()?, + LargeBinary => self.from_large_binary()?, + List(field) => DataType::List(Box::new(self.from_field(field)?)), + Struct(fields) => DataType::Struct(self.from_fields(fields)?), + t => { + return Err(ArrayError::from_arrow(format!( + "unsupported arrow data type: {t:?}" + ))) + } + }) } - #[inline] - fn int256_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Decimal256(arrow_schema::DECIMAL256_MAX_PRECISION, 0) + /// Converts Arrow `LargeUtf8` type to RisingWave data type. + fn from_large_utf8(&self) -> Result { + Ok(DataType::Varchar) } - #[inline] - fn float32_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Float32 + /// Converts Arrow `LargeBinary` type to RisingWave data type. + fn from_large_binary(&self) -> Result { + Ok(DataType::Bytea) } - #[inline] - fn float64_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Float64 + /// Converts Arrow extension type to RisingWave `DataType`. + fn from_extension_type( + &self, + type_name: &str, + physical_type: &arrow_schema::DataType, + ) -> Result { + match (type_name, physical_type) { + ("arrowudf.decimal", arrow_schema::DataType::Utf8) => Ok(DataType::Decimal), + ("arrowudf.json", arrow_schema::DataType::Utf8) => Ok(DataType::Jsonb), + _ => Err(ArrayError::from_arrow(format!( + "unsupported extension type: {type_name:?}" + ))), + } } - #[inline] - fn date_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Date32 + /// Converts Arrow `Array` to RisingWave `ArrayImpl`. + fn from_array( + &self, + field: &arrow_schema::Field, + array: &arrow_array::ArrayRef, + ) -> Result { + use arrow_schema::DataType::*; + use arrow_schema::IntervalUnit::*; + use arrow_schema::TimeUnit::*; + + // extension type + if let Some(type_name) = field.metadata().get("ARROW:extension:name") { + return self.from_extension_array(type_name, array); + } + + match array.data_type() { + Boolean => self.from_bool_array(array.as_any().downcast_ref().unwrap()), + Int16 => self.from_int16_array(array.as_any().downcast_ref().unwrap()), + Int32 => self.from_int32_array(array.as_any().downcast_ref().unwrap()), + Int64 => self.from_int64_array(array.as_any().downcast_ref().unwrap()), + Decimal256(_, _) => self.from_int256_array(array.as_any().downcast_ref().unwrap()), + Float32 => self.from_float32_array(array.as_any().downcast_ref().unwrap()), + Float64 => self.from_float64_array(array.as_any().downcast_ref().unwrap()), + Date32 => self.from_date32_array(array.as_any().downcast_ref().unwrap()), + Time64(Microsecond) => self.from_time64us_array(array.as_any().downcast_ref().unwrap()), + Timestamp(Microsecond, _) => { + self.from_timestampus_array(array.as_any().downcast_ref().unwrap()) + } + Interval(MonthDayNano) => { + self.from_interval_array(array.as_any().downcast_ref().unwrap()) + } + Utf8 => self.from_utf8_array(array.as_any().downcast_ref().unwrap()), + Binary => self.from_binary_array(array.as_any().downcast_ref().unwrap()), + LargeUtf8 => self.from_large_utf8_array(array.as_any().downcast_ref().unwrap()), + LargeBinary => self.from_large_binary_array(array.as_any().downcast_ref().unwrap()), + List(_) => self.from_list_array(array.as_any().downcast_ref().unwrap()), + Struct(_) => self.from_struct_array(array.as_any().downcast_ref().unwrap()), + t => Err(ArrayError::from_arrow(format!( + "unsupported arrow data type: {t:?}", + ))), + } } - #[inline] - fn timestamp_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Timestamp(arrow_schema::TimeUnit::Microsecond, None) + /// Converts Arrow extension array to RisingWave `ArrayImpl`. + fn from_extension_array( + &self, + type_name: &str, + array: &arrow_array::ArrayRef, + ) -> Result { + match type_name { + "arrowudf.decimal" => { + let array: &arrow_array::StringArray = + array.as_any().downcast_ref().ok_or_else(|| { + ArrayError::from_arrow( + "expected string array for `arrowudf.decimal`".to_string(), + ) + })?; + Ok(ArrayImpl::Decimal(array.try_into()?)) + } + "arrowudf.json" => { + let array: &arrow_array::StringArray = + array.as_any().downcast_ref().ok_or_else(|| { + ArrayError::from_arrow( + "expected string array for `arrowudf.json`".to_string(), + ) + })?; + Ok(ArrayImpl::Jsonb(array.try_into()?)) + } + _ => Err(ArrayError::from_arrow(format!( + "unsupported extension type: {type_name:?}" + ))), + } } - #[inline] - fn timestamptz_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Timestamp( - arrow_schema::TimeUnit::Microsecond, - Some("+00:00".into()), - ) + fn from_bool_array(&self, array: &arrow_array::BooleanArray) -> Result { + Ok(ArrayImpl::Bool(array.into())) } - #[inline] - fn time_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Time64(arrow_schema::TimeUnit::Microsecond) + fn from_int16_array(&self, array: &arrow_array::Int16Array) -> Result { + Ok(ArrayImpl::Int16(array.into())) } - #[inline] - fn interval_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Interval(arrow_schema::IntervalUnit::MonthDayNano) + fn from_int32_array(&self, array: &arrow_array::Int32Array) -> Result { + Ok(ArrayImpl::Int32(array.into())) } - #[inline] - fn varchar_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Utf8 + fn from_int64_array(&self, array: &arrow_array::Int64Array) -> Result { + Ok(ArrayImpl::Int64(array.into())) } - #[inline] - fn jsonb_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::LargeUtf8 + fn from_int256_array( + &self, + array: &arrow_array::Decimal256Array, + ) -> Result { + Ok(ArrayImpl::Int256(array.into())) } - #[inline] - fn bytea_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::Binary + fn from_float32_array( + &self, + array: &arrow_array::Float32Array, + ) -> Result { + Ok(ArrayImpl::Float32(array.into())) } - #[inline] - fn decimal_type_to_arrow(&self) -> arrow_schema::DataType { - arrow_schema::DataType::LargeBinary + fn from_float64_array( + &self, + array: &arrow_array::Float64Array, + ) -> Result { + Ok(ArrayImpl::Float64(array.into())) } - #[inline] - fn serial_type_to_arrow(&self) -> arrow_schema::DataType { - todo!("serial type is not supported to convert to arrow") + fn from_date32_array(&self, array: &arrow_array::Date32Array) -> Result { + Ok(ArrayImpl::Date(array.into())) } - #[inline] - fn list_type_to_arrow( + fn from_time64us_array( &self, - datatype: &DataType, - ) -> Result { - Ok(arrow_schema::DataType::List(Arc::new( - arrow_schema::Field::new("item", datatype.try_into()?, true), - ))) + array: &arrow_array::Time64MicrosecondArray, + ) -> Result { + Ok(ArrayImpl::Time(array.into())) } - #[inline] - fn struct_type_to_arrow( + fn from_timestampus_array( &self, - fields: &StructType, - ) -> Result { - Ok(arrow_schema::DataType::Struct( - fields - .iter() - .map(|(name, ty)| Ok(arrow_schema::Field::new(name, ty.try_into()?, true))) - .try_collect::<_, _, ArrayError>()?, - )) + array: &arrow_array::TimestampMicrosecondArray, + ) -> Result { + Ok(ArrayImpl::Timestamp(array.into())) } -} -struct DefaultArrowConvert; -impl ToArrowArrayConvert for DefaultArrowConvert {} - -/// Implement bi-directional `From` between `ArrayImpl` and `arrow_array::ArrayRef`. -macro_rules! converts_generic { - ($({ $ArrowType:ty, $ArrowPattern:pat, $ArrayImplPattern:path }),*) => { - // RisingWave array -> Arrow array - impl TryFrom<&ArrayImpl> for arrow_array::ArrayRef { - type Error = ArrayError; - fn try_from(array: &ArrayImpl) -> Result { - DefaultArrowConvert{}.to_arrow(array) - } - } - // Arrow array -> RisingWave array - impl TryFrom<&arrow_array::ArrayRef> for ArrayImpl { - type Error = ArrayError; - fn try_from(array: &arrow_array::ArrayRef) -> Result { - use arrow_schema::DataType::*; - use arrow_schema::IntervalUnit::*; - use arrow_schema::TimeUnit::*; - match array.data_type() { - $($ArrowPattern => Ok($ArrayImplPattern( - array - .as_any() - .downcast_ref::<$ArrowType>() - .unwrap() - .try_into()?, - )),)* - Timestamp(Microsecond, Some(_)) => Ok(ArrayImpl::Timestamptz( - array - .as_any() - .downcast_ref::() - .unwrap() - .try_into()?, - )), - // This arrow decimal type is used by iceberg source to read iceberg decimal into RW decimal. - Decimal128(_, _) => Ok(ArrayImpl::Decimal( - array - .as_any() - .downcast_ref::() - .unwrap() - .try_into()?, - )), - t => Err(ArrayError::from_arrow(format!("unsupported data type: {t:?}"))), - } - } - } - }; -} -converts_generic! { - { arrow_array::Int16Array, Int16, ArrayImpl::Int16 }, - { arrow_array::Int32Array, Int32, ArrayImpl::Int32 }, - { arrow_array::Int64Array, Int64, ArrayImpl::Int64 }, - { arrow_array::Float32Array, Float32, ArrayImpl::Float32 }, - { arrow_array::Float64Array, Float64, ArrayImpl::Float64 }, - { arrow_array::StringArray, Utf8, ArrayImpl::Utf8 }, - { arrow_array::BooleanArray, Boolean, ArrayImpl::Bool }, - // Arrow doesn't have a data type to represent unconstrained numeric (`DECIMAL` in RisingWave and - // Postgres). So we pick a special type `LargeBinary` for it. - // Values stored in the array are the string representation of the decimal. e.g. b"1.234", b"+inf" - { arrow_array::LargeBinaryArray, LargeBinary, ArrayImpl::Decimal }, - { arrow_array::Decimal256Array, Decimal256(_, _), ArrayImpl::Int256 }, - { arrow_array::Date32Array, Date32, ArrayImpl::Date }, - { arrow_array::TimestampMicrosecondArray, Timestamp(Microsecond, None), ArrayImpl::Timestamp }, - { arrow_array::Time64MicrosecondArray, Time64(Microsecond), ArrayImpl::Time }, - { arrow_array::IntervalMonthDayNanoArray, Interval(MonthDayNano), ArrayImpl::Interval }, - { arrow_array::StructArray, Struct(_), ArrayImpl::Struct }, - { arrow_array::ListArray, List(_), ArrayImpl::List }, - { arrow_array::BinaryArray, Binary, ArrayImpl::Bytea }, - { arrow_array::LargeStringArray, LargeUtf8, ArrayImpl::Jsonb } // we use LargeUtf8 to represent Jsonb in arrow -} - -// Arrow Datatype -> Risingwave Datatype -impl From<&arrow_schema::DataType> for DataType { - fn from(value: &arrow_schema::DataType) -> Self { - use arrow_schema::DataType::*; - use arrow_schema::IntervalUnit::*; - use arrow_schema::TimeUnit::*; - match value { - Boolean => Self::Boolean, - Int16 => Self::Int16, - Int32 => Self::Int32, - Int64 => Self::Int64, - Float32 => Self::Float32, - Float64 => Self::Float64, - LargeBinary => Self::Decimal, - Decimal256(_, _) => Self::Int256, - Date32 => Self::Date, - Time64(Microsecond) => Self::Time, - Timestamp(Microsecond, None) => Self::Timestamp, - Timestamp(Microsecond, Some(_)) => Self::Timestamptz, - Interval(MonthDayNano) => Self::Interval, - Binary => Self::Bytea, - Utf8 => Self::Varchar, - LargeUtf8 => Self::Jsonb, - Struct(fields) => Self::Struct(fields.into()), - List(field) => Self::List(Box::new(field.data_type().into())), - Decimal128(_, _) => Self::Decimal, - _ => todo!("Unsupported arrow data type: {value:?}"), - } + fn from_interval_array( + &self, + array: &arrow_array::IntervalMonthDayNanoArray, + ) -> Result { + Ok(ArrayImpl::Interval(array.into())) } -} -impl From<&arrow_schema::Fields> for StructType { - fn from(fields: &arrow_schema::Fields) -> Self { - Self::new( - fields - .iter() - .map(|f| (f.name().clone(), f.data_type().into())) - .collect(), - ) + fn from_utf8_array(&self, array: &arrow_array::StringArray) -> Result { + Ok(ArrayImpl::Utf8(array.into())) } -} - -impl TryFrom<&StructType> for arrow_schema::Fields { - type Error = ArrayError; - fn try_from(struct_type: &StructType) -> Result { - struct_type - .iter() - .map(|(name, ty)| Ok(arrow_schema::Field::new(name, ty.try_into()?, true))) - .try_collect() + fn from_binary_array(&self, array: &arrow_array::BinaryArray) -> Result { + Ok(ArrayImpl::Bytea(array.into())) } -} -impl From for DataType { - fn from(value: arrow_schema::DataType) -> Self { - (&value).into() + fn from_large_utf8_array( + &self, + array: &arrow_array::LargeStringArray, + ) -> Result { + Ok(ArrayImpl::Utf8(array.into())) } -} - -struct DefaultArrowTypeConvert; - -impl ToArrowTypeConvert for DefaultArrowTypeConvert {} -impl TryFrom<&DataType> for arrow_schema::DataType { - type Error = ArrayError; - - fn try_from(value: &DataType) -> Result { - DefaultArrowTypeConvert {}.to_arrow_type(value) + fn from_large_binary_array( + &self, + array: &arrow_array::LargeBinaryArray, + ) -> Result { + Ok(ArrayImpl::Bytea(array.into())) } -} -impl TryFrom for arrow_schema::DataType { - type Error = ArrayError; + fn from_list_array(&self, array: &arrow_array::ListArray) -> Result { + use arrow_array::Array; + let arrow_schema::DataType::List(field) = array.data_type() else { + panic!("nested field types cannot be determined."); + }; + Ok(ArrayImpl::List(ListArray { + value: Box::new(self.from_array(field, array.values())?), + bitmap: match array.nulls() { + Some(nulls) => nulls.iter().collect(), + None => Bitmap::ones(array.len()), + }, + offsets: array.offsets().iter().map(|o| *o as u32).collect(), + })) + } - fn try_from(value: DataType) -> Result { - (&value).try_into() + fn from_struct_array(&self, array: &arrow_array::StructArray) -> Result { + use arrow_array::Array; + let arrow_schema::DataType::Struct(fields) = array.data_type() else { + panic!("nested field types cannot be determined."); + }; + Ok(ArrayImpl::Struct(StructArray::new( + self.from_fields(fields)?, + array + .columns() + .iter() + .zip_eq_fast(fields) + .map(|(array, field)| self.from_array(field, array).map(Arc::new)) + .try_collect()?, + (0..array.len()).map(|i| array.is_valid(i)).collect(), + ))) } } @@ -830,12 +756,15 @@ converts!(I64Array, arrow_array::Int64Array); converts!(F32Array, arrow_array::Float32Array, @map); converts!(F64Array, arrow_array::Float64Array, @map); converts!(BytesArray, arrow_array::BinaryArray); +converts!(BytesArray, arrow_array::LargeBinaryArray); converts!(Utf8Array, arrow_array::StringArray); +converts!(Utf8Array, arrow_array::LargeStringArray); converts!(DateArray, arrow_array::Date32Array, @map); converts!(TimeArray, arrow_array::Time64MicrosecondArray, @map); converts!(TimestampArray, arrow_array::TimestampMicrosecondArray, @map); converts!(TimestamptzArray, arrow_array::TimestampMicrosecondArray, @map); converts!(IntervalArray, arrow_array::IntervalMonthDayNanoArray, @map); +converts!(SerialArray, arrow_array::Int64Array, @map); /// Converts RisingWave value from and into Arrow value. pub trait FromIntoArrow { @@ -845,6 +774,18 @@ pub trait FromIntoArrow { fn into_arrow(self) -> Self::ArrowType; } +impl FromIntoArrow for Serial { + type ArrowType = i64; + + fn from_arrow(value: Self::ArrowType) -> Self { + value.into() + } + + fn into_arrow(self) -> Self::ArrowType { + self.into() + } +} + impl FromIntoArrow for F32 { type ArrowType = f32; @@ -907,11 +848,9 @@ impl FromIntoArrow for Timestamp { fn from_arrow(value: Self::ArrowType) -> Self { Timestamp( - NaiveDateTime::from_timestamp_opt( - (value / 1_000_000) as _, - (value % 1_000_000 * 1000) as _, - ) - .unwrap(), + DateTime::from_timestamp((value / 1_000_000) as _, (value % 1_000_000 * 1000) as _) + .unwrap() + .naive_utc(), ) } @@ -936,29 +875,14 @@ impl FromIntoArrow for Timestamptz { } impl FromIntoArrow for Interval { - type ArrowType = i128; + type ArrowType = ArrowIntervalType; fn from_arrow(value: Self::ArrowType) -> Self { - // XXX: the arrow-rs decoding is incorrect - // let (months, days, ns) = arrow_array::types::IntervalMonthDayNanoType::to_parts(value); - let months = value as i32; - let days = (value >> 32) as i32; - let ns = (value >> 64) as i64; - Interval::from_month_day_usec(months, days, ns / 1000) + ::to_interval(value) } fn into_arrow(self) -> Self::ArrowType { - // XXX: the arrow-rs encoding is incorrect - // arrow_array::types::IntervalMonthDayNanoType::make_value( - // self.months(), - // self.days(), - // // TODO: this may overflow and we need `try_into` - // self.usecs() * 1000, - // ) - let m = self.months() as u128 & u32::MAX as u128; - let d = (self.days() as u128 & u32::MAX as u128) << 32; - let n = ((self.usecs() * 1000) as u128 & u64::MAX as u128) << 64; - (m | d | n) as i128 + ::from_interval(self) } } @@ -973,6 +897,17 @@ impl From<&DecimalArray> for arrow_array::LargeBinaryArray { } } +impl From<&DecimalArray> for arrow_array::StringArray { + fn from(array: &DecimalArray) -> Self { + let mut builder = + arrow_array::builder::StringBuilder::with_capacity(array.len(), array.len() * 8); + for value in array.iter() { + builder.append_option(value.map(|d| d.to_string())); + } + builder.finish() + } +} + // This arrow decimal type is used by iceberg source to read iceberg decimal into RW decimal. impl TryFrom<&arrow_array::Decimal128Array> for DecimalArray { type Error = ArrayError; @@ -1020,6 +955,57 @@ impl TryFrom<&arrow_array::LargeBinaryArray> for DecimalArray { } } +impl TryFrom<&arrow_array::StringArray> for DecimalArray { + type Error = ArrayError; + + fn try_from(array: &arrow_array::StringArray) -> Result { + array + .iter() + .map(|o| { + o.map(|s| { + s.parse() + .map_err(|_| ArrayError::from_arrow(format!("invalid decimal: {s:?}"))) + }) + .transpose() + }) + .try_collect() + } +} + +impl From<&JsonbArray> for arrow_array::StringArray { + fn from(array: &JsonbArray) -> Self { + let mut builder = + arrow_array::builder::StringBuilder::with_capacity(array.len(), array.len() * 16); + for value in array.iter() { + match value { + Some(jsonb) => { + write!(&mut builder, "{}", jsonb).unwrap(); + builder.append_value(""); + } + None => builder.append_null(), + } + } + builder.finish() + } +} + +impl TryFrom<&arrow_array::StringArray> for JsonbArray { + type Error = ArrayError; + + fn try_from(array: &arrow_array::StringArray) -> Result { + array + .iter() + .map(|o| { + o.map(|s| { + s.parse() + .map_err(|_| ArrayError::from_arrow(format!("invalid json: {s}"))) + }) + .transpose() + }) + .try_collect() + } +} + impl From<&JsonbArray> for arrow_array::LargeStringArray { fn from(array: &JsonbArray) -> Self { let mut builder = @@ -1088,195 +1074,8 @@ impl From<&arrow_array::Decimal256Array> for Int256Array { } } -impl TryFrom<&ListArray> for arrow_array::ListArray { - type Error = ArrayError; - - fn try_from(array: &ListArray) -> Result { - use arrow_array::builder::*; - fn build( - array: &ListArray, - a: &A, - builder: B, - mut append: F, - ) -> arrow_array::ListArray - where - A: Array, - B: arrow_array::builder::ArrayBuilder, - F: FnMut(&mut B, Option>), - { - let mut builder = ListBuilder::with_capacity(builder, a.len()); - for i in 0..array.len() { - for j in array.offsets[i]..array.offsets[i + 1] { - append(builder.values(), a.value_at(j as usize)); - } - builder.append(!array.is_null(i)); - } - builder.finish() - } - Ok(match &*array.value { - ArrayImpl::Int16(a) => build(array, a, Int16Builder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }), - ArrayImpl::Int32(a) => build(array, a, Int32Builder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }), - ArrayImpl::Int64(a) => build(array, a, Int64Builder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }), - - ArrayImpl::Float32(a) => { - build(array, a, Float32Builder::with_capacity(a.len()), |b, v| { - b.append_option(v.map(|f| f.0)) - }) - } - ArrayImpl::Float64(a) => { - build(array, a, Float64Builder::with_capacity(a.len()), |b, v| { - b.append_option(v.map(|f| f.0)) - }) - } - ArrayImpl::Utf8(a) => build( - array, - a, - StringBuilder::with_capacity(a.len(), a.data().len()), - |b, v| b.append_option(v), - ), - ArrayImpl::Int256(a) => build( - array, - a, - Decimal256Builder::with_capacity(a.len()).with_data_type( - arrow_schema::DataType::Decimal256(arrow_schema::DECIMAL256_MAX_PRECISION, 0), - ), - |b, v| b.append_option(v.map(Into::into)), - ), - ArrayImpl::Bool(a) => { - build(array, a, BooleanBuilder::with_capacity(a.len()), |b, v| { - b.append_option(v) - }) - } - ArrayImpl::Decimal(a) => build( - array, - a, - LargeBinaryBuilder::with_capacity(a.len(), a.len() * 8), - |b, v| b.append_option(v.map(|d| d.to_string())), - ), - ArrayImpl::Interval(a) => build( - array, - a, - IntervalMonthDayNanoBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Date(a) => build(array, a, Date32Builder::with_capacity(a.len()), |b, v| { - b.append_option(v.map(|d| d.into_arrow())) - }), - ArrayImpl::Timestamp(a) => build( - array, - a, - TimestampMicrosecondBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Timestamptz(a) => build( - array, - a, - TimestampMicrosecondBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Time(a) => build( - array, - a, - Time64MicrosecondBuilder::with_capacity(a.len()), - |b, v| b.append_option(v.map(|d| d.into_arrow())), - ), - ArrayImpl::Jsonb(a) => build( - array, - a, - LargeStringBuilder::with_capacity(a.len(), a.len() * 16), - |b, v| b.append_option(v.map(|j| j.to_string())), - ), - ArrayImpl::Serial(_) => todo!("list of serial"), - ArrayImpl::Struct(a) => { - let values = Arc::new(arrow_array::StructArray::try_from(a)?); - arrow_array::ListArray::new( - Arc::new(arrow_schema::Field::new( - "item", - a.data_type().try_into()?, - true, - )), - arrow_buffer::OffsetBuffer::new(arrow_buffer::ScalarBuffer::from( - array - .offsets() - .iter() - .map(|o| *o as i32) - .collect::>(), - )), - values, - Some(array.null_bitmap().into()), - ) - } - ArrayImpl::List(_) => todo!("list of list"), - ArrayImpl::Bytea(a) => build( - array, - a, - BinaryBuilder::with_capacity(a.len(), a.data().len()), - |b, v| b.append_option(v), - ), - }) - } -} - -impl TryFrom<&arrow_array::ListArray> for ListArray { - type Error = ArrayError; - - fn try_from(array: &arrow_array::ListArray) -> Result { - use arrow_array::Array; - Ok(ListArray { - value: Box::new(ArrayImpl::try_from(array.values())?), - bitmap: match array.nulls() { - Some(nulls) => nulls.iter().collect(), - None => Bitmap::ones(array.len()), - }, - offsets: array.offsets().iter().map(|o| *o as u32).collect(), - }) - } -} - -impl TryFrom<&StructArray> for arrow_array::StructArray { - type Error = ArrayError; - - fn try_from(array: &StructArray) -> Result { - Ok(arrow_array::StructArray::new( - array.data_type().as_struct().try_into()?, - array - .fields() - .map(|arr| arr.as_ref().try_into()) - .try_collect::<_, _, ArrayError>()?, - Some(array.null_bitmap().into()), - )) - } -} - -impl TryFrom<&arrow_array::StructArray> for StructArray { - type Error = ArrayError; - - fn try_from(array: &arrow_array::StructArray) -> Result { - use arrow_array::Array; - let arrow_schema::DataType::Struct(fields) = array.data_type() else { - panic!("nested field types cannot be determined."); - }; - Ok(StructArray::new( - fields.into(), - array - .columns() - .iter() - .map(|a| ArrayImpl::try_from(a).map(Arc::new)) - .try_collect()?, - (0..array.len()).map(|i| !array.is_null(i)).collect(), - )) - } -} - #[cfg(test)] mod tests { - use super::arrow_array::Array as _; use super::*; #[test] @@ -1293,6 +1092,20 @@ mod tests { assert_eq!(I16Array::from(&arrow), array); } + #[test] + fn i32() { + let array = I32Array::from_iter([None, Some(-7), Some(25)]); + let arrow = arrow_array::Int32Array::from(&array); + assert_eq!(I32Array::from(&arrow), array); + } + + #[test] + fn i64() { + let array = I64Array::from_iter([None, Some(-7), Some(25)]); + let arrow = arrow_array::Int64Array::from(&array); + assert_eq!(I64Array::from(&arrow), array); + } + #[test] fn f32() { let array = F32Array::from_iter([None, Some(-7.0), Some(25.0)]); @@ -1300,6 +1113,13 @@ mod tests { assert_eq!(F32Array::from(&arrow), array); } + #[test] + fn f64() { + let array = F64Array::from_iter([None, Some(-7.0), Some(25.0)]); + let arrow = arrow_array::Float64Array::from(&array); + assert_eq!(F64Array::from(&arrow), array); + } + #[test] fn date() { let array = DateArray::from_iter([ @@ -1352,6 +1172,13 @@ mod tests { assert_eq!(Utf8Array::from(&arrow), array); } + #[test] + fn binary() { + let array = BytesArray::from_iter([None, Some("array".as_bytes())]); + let arrow = arrow_array::BinaryArray::from(&array); + assert_eq!(BytesArray::from(&arrow), array); + } + #[test] fn decimal() { let array = DecimalArray::from_iter([ @@ -1364,6 +1191,9 @@ mod tests { ]); let arrow = arrow_array::LargeBinaryArray::from(&array); assert_eq!(DecimalArray::try_from(&arrow).unwrap(), array); + + let arrow = arrow_array::StringArray::from(&array); + assert_eq!(DecimalArray::try_from(&arrow).unwrap(), array); } #[test] @@ -1378,6 +1208,9 @@ mod tests { ]); let arrow = arrow_array::LargeStringArray::from(&array); assert_eq!(JsonbArray::try_from(&arrow).unwrap(), array); + + let arrow = arrow_array::StringArray::from(&array); + assert_eq!(JsonbArray::try_from(&arrow).unwrap(), array); } #[test] @@ -1403,62 +1236,4 @@ mod tests { let arrow = arrow_array::Decimal256Array::from(&array); assert_eq!(Int256Array::from(&arrow), array); } - - #[test] - fn struct_array() { - // Empty array - risingwave to arrow conversion. - let test_arr = StructArray::new(StructType::empty(), vec![], Bitmap::ones(0)); - assert_eq!( - arrow_array::StructArray::try_from(&test_arr).unwrap().len(), - 0 - ); - - // Empty array - arrow to risingwave conversion. - let test_arr_2 = arrow_array::StructArray::from(vec![]); - assert_eq!(StructArray::try_from(&test_arr_2).unwrap().len(), 0); - - // Struct array with primitive types. arrow to risingwave conversion. - let test_arrow_struct_array = arrow_array::StructArray::try_from(vec![ - ( - "a", - Arc::new(arrow_array::BooleanArray::from(vec![ - Some(false), - Some(false), - Some(true), - None, - ])) as arrow_array::ArrayRef, - ), - ( - "b", - Arc::new(arrow_array::Int32Array::from(vec![ - Some(42), - Some(28), - Some(19), - None, - ])) as arrow_array::ArrayRef, - ), - ]) - .unwrap(); - let actual_risingwave_struct_array = - StructArray::try_from(&test_arrow_struct_array).unwrap(); - let expected_risingwave_struct_array = StructArray::new( - StructType::new(vec![("a", DataType::Boolean), ("b", DataType::Int32)]), - vec![ - BoolArray::from_iter([Some(false), Some(false), Some(true), None]).into_ref(), - I32Array::from_iter([Some(42), Some(28), Some(19), None]).into_ref(), - ], - [true, true, true, true].into_iter().collect(), - ); - assert_eq!( - expected_risingwave_struct_array, - actual_risingwave_struct_array - ); - } - - #[test] - fn list() { - let array = ListArray::from_iter([None, Some(vec![0, -127, 127, 50]), Some(vec![0; 0])]); - let arrow = arrow_array::ListArray::try_from(&array).unwrap(); - assert_eq!(ListArray::try_from(&arrow).unwrap(), array); - } } diff --git a/src/common/src/array/arrow/arrow_udf.rs b/src/common/src/array/arrow/arrow_udf.rs new file mode 100644 index 0000000000000..e461f49e576a6 --- /dev/null +++ b/src/common/src/array/arrow/arrow_udf.rs @@ -0,0 +1,208 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +//! This is for arrow dependency named `arrow-xxx` such as `arrow-array` in the cargo workspace. +//! +//! This should the default arrow version to be used in our system. +//! +//! The corresponding version of arrow is currently used by `udf` and `iceberg` sink. + +use std::sync::Arc; + +pub use arrow_impl::{FromArrow, ToArrow}; +use {arrow_array, arrow_buffer, arrow_cast, arrow_schema}; +type ArrowIntervalType = i128; + +use crate::array::{ArrayError, ArrayImpl, DataType, DecimalArray, JsonbArray}; + +#[expect(clippy::duplicate_mod)] +#[path = "./arrow_impl.rs"] +mod arrow_impl; + +/// Arrow conversion for UDF. +#[derive(Default, Debug)] +pub struct UdfArrowConvert { + /// Whether the UDF talks in legacy mode. + /// + /// If true, decimal and jsonb types are mapped to Arrow `LargeBinary` and `LargeUtf8` types. + /// Otherwise, they are mapped to Arrow extension types. + /// See . + pub legacy: bool, +} + +impl ToArrow for UdfArrowConvert { + fn decimal_to_arrow( + &self, + _data_type: &arrow_schema::DataType, + array: &DecimalArray, + ) -> Result { + if self.legacy { + // Decimal values are stored as ASCII text representation in a large binary array. + Ok(Arc::new(arrow_array::LargeBinaryArray::from(array))) + } else { + Ok(Arc::new(arrow_array::StringArray::from(array))) + } + } + + fn jsonb_to_arrow(&self, array: &JsonbArray) -> Result { + if self.legacy { + // JSON values are stored as text representation in a large string array. + Ok(Arc::new(arrow_array::LargeStringArray::from(array))) + } else { + Ok(Arc::new(arrow_array::StringArray::from(array))) + } + } + + fn jsonb_type_to_arrow(&self, name: &str) -> arrow_schema::Field { + if self.legacy { + arrow_schema::Field::new(name, arrow_schema::DataType::LargeUtf8, true) + } else { + arrow_schema::Field::new(name, arrow_schema::DataType::Utf8, true) + .with_metadata([("ARROW:extension:name".into(), "arrowudf.json".into())].into()) + } + } + + fn decimal_type_to_arrow(&self, name: &str) -> arrow_schema::Field { + if self.legacy { + arrow_schema::Field::new(name, arrow_schema::DataType::LargeBinary, true) + } else { + arrow_schema::Field::new(name, arrow_schema::DataType::Utf8, true) + .with_metadata([("ARROW:extension:name".into(), "arrowudf.decimal".into())].into()) + } + } +} + +impl FromArrow for UdfArrowConvert { + fn from_large_utf8(&self) -> Result { + if self.legacy { + Ok(DataType::Jsonb) + } else { + Ok(DataType::Varchar) + } + } + + fn from_large_binary(&self) -> Result { + if self.legacy { + Ok(DataType::Decimal) + } else { + Ok(DataType::Bytea) + } + } + + fn from_large_utf8_array( + &self, + array: &arrow_array::LargeStringArray, + ) -> Result { + if self.legacy { + Ok(ArrayImpl::Jsonb(array.try_into()?)) + } else { + Ok(ArrayImpl::Utf8(array.into())) + } + } + + fn from_large_binary_array( + &self, + array: &arrow_array::LargeBinaryArray, + ) -> Result { + if self.legacy { + Ok(ArrayImpl::Decimal(array.try_into()?)) + } else { + Ok(ArrayImpl::Bytea(array.into())) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::array::*; + + #[test] + fn struct_array() { + // Empty array - risingwave to arrow conversion. + let test_arr = StructArray::new(StructType::empty(), vec![], Bitmap::ones(0)); + assert_eq!( + UdfArrowConvert::default() + .struct_to_arrow( + &arrow_schema::DataType::Struct(arrow_schema::Fields::empty()), + &test_arr + ) + .unwrap() + .len(), + 0 + ); + + // Empty array - arrow to risingwave conversion. + let test_arr_2 = arrow_array::StructArray::from(vec![]); + assert_eq!( + UdfArrowConvert::default() + .from_struct_array(&test_arr_2) + .unwrap() + .len(), + 0 + ); + + // Struct array with primitive types. arrow to risingwave conversion. + let test_arrow_struct_array = arrow_array::StructArray::try_from(vec![ + ( + "a", + Arc::new(arrow_array::BooleanArray::from(vec![ + Some(false), + Some(false), + Some(true), + None, + ])) as arrow_array::ArrayRef, + ), + ( + "b", + Arc::new(arrow_array::Int32Array::from(vec![ + Some(42), + Some(28), + Some(19), + None, + ])) as arrow_array::ArrayRef, + ), + ]) + .unwrap(); + let actual_risingwave_struct_array = UdfArrowConvert::default() + .from_struct_array(&test_arrow_struct_array) + .unwrap() + .into_struct(); + let expected_risingwave_struct_array = StructArray::new( + StructType::new(vec![("a", DataType::Boolean), ("b", DataType::Int32)]), + vec![ + BoolArray::from_iter([Some(false), Some(false), Some(true), None]).into_ref(), + I32Array::from_iter([Some(42), Some(28), Some(19), None]).into_ref(), + ], + [true, true, true, true].into_iter().collect(), + ); + assert_eq!( + expected_risingwave_struct_array, + actual_risingwave_struct_array + ); + } + + #[test] + fn list() { + let array = ListArray::from_iter([None, Some(vec![0, -127, 127, 50]), Some(vec![0; 0])]); + let data_type = arrow_schema::DataType::new_list(arrow_schema::DataType::Int32, true); + let arrow = UdfArrowConvert::default() + .list_to_arrow(&data_type, &array) + .unwrap(); + let rw_array = UdfArrowConvert::default() + .from_list_array(arrow.as_any().downcast_ref().unwrap()) + .unwrap(); + assert_eq!(rw_array.as_list(), &array); + } +} diff --git a/src/common/src/array/arrow/mod.rs b/src/common/src/array/arrow/mod.rs index 4baea60f11b3e..fd9f55ee09f7e 100644 --- a/src/common/src/array/arrow/mod.rs +++ b/src/common/src/array/arrow/mod.rs @@ -12,12 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod arrow_default; mod arrow_deltalake; mod arrow_iceberg; +mod arrow_udf; -pub use arrow_default::{ - to_record_batch_with_schema, ToArrowArrayWithTypeConvert, ToArrowTypeConvert, -}; -pub use arrow_deltalake::to_deltalake_record_batch_with_schema; -pub use arrow_iceberg::{iceberg_to_arrow_type, to_iceberg_record_batch_with_schema}; +pub use arrow_deltalake::DeltaLakeConvert; +pub use arrow_iceberg::IcebergArrowConvert; +pub use arrow_udf::{FromArrow, ToArrow, UdfArrowConvert}; + +use crate::types::Interval; + +trait ArrowIntervalTypeTrait { + fn to_interval(self) -> Interval; + fn from_interval(value: Interval) -> Self; +} + +impl ArrowIntervalTypeTrait for i128 { + fn to_interval(self) -> Interval { + // XXX: the arrow-rs decoding is incorrect + // let (months, days, ns) = arrow_array::types::IntervalMonthDayNanoType::to_parts(value); + let months = self as i32; + let days = (self >> 32) as i32; + let ns = (self >> 64) as i64; + Interval::from_month_day_usec(months, days, ns / 1000) + } + + fn from_interval(value: Interval) -> i128 { + // XXX: the arrow-rs encoding is incorrect + // arrow_array::types::IntervalMonthDayNanoType::make_value( + // self.months(), + // self.days(), + // // TODO: this may overflow and we need `try_into` + // self.usecs() * 1000, + // ) + let m = value.months() as u128 & u32::MAX as u128; + let d = (value.days() as u128 & u32::MAX as u128) << 32; + let n = ((value.usecs() * 1000) as u128 & u64::MAX as u128) << 64; + (m | d | n) as i128 + } +} diff --git a/src/common/src/array/bool_array.rs b/src/common/src/array/bool_array.rs index ebee50c8e4b72..7ed0e4b703a22 100644 --- a/src/common/src/array/bool_array.rs +++ b/src/common/src/array/bool_array.rs @@ -16,7 +16,7 @@ use risingwave_common_estimate_size::EstimateSize; use risingwave_pb::data::{ArrayType, PbArray}; use super::{Array, ArrayBuilder, DataType}; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct BoolArray { diff --git a/src/common/src/array/bytes_array.rs b/src/common/src/array/bytes_array.rs index 1257730b63c96..0d62c3d26c6e3 100644 --- a/src/common/src/array/bytes_array.rs +++ b/src/common/src/array/bytes_array.rs @@ -21,7 +21,7 @@ use risingwave_pb::common::Buffer; use risingwave_pb::data::{ArrayType, PbArray}; use super::{Array, ArrayBuilder, DataType}; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::util::iter_util::ZipEqDebug; /// `BytesArray` is a collection of Rust `[u8]`s. @@ -108,12 +108,6 @@ impl Array for BytesArray { } } -impl BytesArray { - pub(super) fn data(&self) -> &[u8] { - &self.data - } -} - impl<'a> FromIterator> for BytesArray { fn from_iter>>(iter: I) -> Self { let iter = iter.into_iter(); @@ -148,19 +142,24 @@ pub struct BytesArrayBuilder { impl ArrayBuilder for BytesArrayBuilder { type ArrayType = BytesArray; - fn new(capacity: usize) -> Self { - let mut offset = Vec::with_capacity(capacity + 1); + /// Creates a new `BytesArrayBuilder`. + /// + /// `item_capacity` is the number of items to pre-allocate. The size of the preallocated + /// buffer of offsets is the number of items plus one. + /// No additional memory is pre-allocated for the data buffer. + fn new(item_capacity: usize) -> Self { + let mut offset = Vec::with_capacity(item_capacity + 1); offset.push(0); Self { offset, - data: Vec::with_capacity(capacity), - bitmap: BitmapBuilder::with_capacity(capacity), + data: Vec::with_capacity(0), + bitmap: BitmapBuilder::with_capacity(item_capacity), } } - fn with_type(capacity: usize, ty: DataType) -> Self { + fn with_type(item_capacity: usize, ty: DataType) -> Self { assert_eq!(ty, DataType::Bytea); - Self::new(capacity) + Self::new(item_capacity) } fn append_n<'a>(&'a mut self, n: usize, value: Option<&'a [u8]>) { diff --git a/src/common/src/array/data_chunk.rs b/src/common/src/array/data_chunk.rs index 849979abc5155..a53d9930eba69 100644 --- a/src/common/src/array/data_chunk.rs +++ b/src/common/src/array/data_chunk.rs @@ -13,10 +13,10 @@ // limitations under the License. use std::borrow::Cow; +use std::fmt; use std::fmt::Display; use std::hash::BuildHasher; use std::sync::Arc; -use std::{fmt, usize}; use bytes::Bytes; use either::Either; @@ -29,7 +29,7 @@ use risingwave_pb::data::PbDataChunk; use super::{Array, ArrayImpl, ArrayRef, ArrayResult, StructArray}; use crate::array::data_chunk_iter::RowRef; use crate::array::ArrayBuilderImpl; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::field_generator::{FieldGeneratorImpl, VarcharProperty}; use crate::hash::HashCode; use crate::row::Row; @@ -140,11 +140,13 @@ impl DataChunk { self.columns.len() } + // TODO(rc): shall we rename this to `visible_size`? I sometimes find this confused with `capacity`. /// `cardinality` returns the number of visible tuples pub fn cardinality(&self) -> usize { self.visibility.count_ones() } + // TODO(rc): shall we rename this to `size`? /// `capacity` returns physical length of any chunk column pub fn capacity(&self) -> usize { self.visibility.len() @@ -247,6 +249,7 @@ impl DataChunk { Self::new(columns, Bitmap::ones(cardinality)) } + /// Scatter a compacted chunk to a new chunk with the given visibility. pub fn uncompact(self, vis: Bitmap) -> Self { let mut uncompact_builders: Vec<_> = self .columns @@ -801,7 +804,7 @@ impl DataChunkTestExt for DataChunk { let arr = col; let mut builder = arr.create_builder(n * 2); for v in arr.iter() { - builder.append(&v.to_owned_datum()); + builder.append(v.to_owned_datum()); builder.append_null(); } diff --git a/src/common/src/array/jsonb_array.rs b/src/common/src/array/jsonb_array.rs index 4a0df2f55835d..b49b741976714 100644 --- a/src/common/src/array/jsonb_array.rs +++ b/src/common/src/array/jsonb_array.rs @@ -16,7 +16,7 @@ use risingwave_common_estimate_size::EstimateSize; use risingwave_pb::data::{PbArray, PbArrayType}; use super::{Array, ArrayBuilder, ArrayImpl, ArrayResult}; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::types::{DataType, JsonbRef, JsonbVal, Scalar}; #[derive(Debug, Clone, EstimateSize)] diff --git a/src/common/src/array/list_array.rs b/src/common/src/array/list_array.rs index dae9a4a94bc93..7fc1fdecee6fe 100644 --- a/src/common/src/array/list_array.rs +++ b/src/common/src/array/list_array.rs @@ -29,7 +29,7 @@ use super::{ Array, ArrayBuilder, ArrayBuilderImpl, ArrayImpl, ArrayResult, BoolArray, PrimitiveArray, PrimitiveArrayItemType, RowRef, Utf8Array, }; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::row::Row; use crate::types::{ hash_datum, DataType, Datum, DatumRef, DefaultOrd, Scalar, ScalarImpl, ScalarRefImpl, @@ -239,6 +239,11 @@ impl ListArray { } } + /// Return the inner array of the list array. + pub fn values(&self) -> &ArrayImpl { + &self.value + } + pub fn from_protobuf(array: &PbArray) -> ArrayResult { ensure!( array.values.is_empty(), diff --git a/src/common/src/array/mod.rs b/src/common/src/array/mod.rs index 93f6255038e9e..f2ae95aa71efb 100644 --- a/src/common/src/array/mod.rs +++ b/src/common/src/array/mod.rs @@ -14,11 +14,7 @@ //! `Array` defines all in-memory representations of vectorized execution framework. -mod arrow; -pub use arrow::{ - iceberg_to_arrow_type, to_deltalake_record_batch_with_schema, - to_iceberg_record_batch_with_schema, to_record_batch_with_schema, -}; +pub mod arrow; mod bool_array; pub mod bytes_array; mod chrono_array; @@ -69,7 +65,7 @@ pub use utf8_array::*; pub use self::error::ArrayError; pub use crate::array::num256_array::{Int256Array, Int256ArrayBuilder}; -use crate::buffer::Bitmap; +use crate::bitmap::Bitmap; use crate::types::*; use crate::{dispatch_array_builder_variants, dispatch_array_variants, for_all_array_variants}; pub type ArrayResult = Result; @@ -710,7 +706,7 @@ mod test_util { use std::hash::{BuildHasher, Hasher}; use super::Array; - use crate::buffer::Bitmap; + use crate::bitmap::Bitmap; use crate::util::iter_util::ZipEqFast; pub fn hash_finish(hashers: &[H]) -> Vec { diff --git a/src/common/src/array/num256_array.rs b/src/common/src/array/num256_array.rs index 48ee99ac21db2..ad8611a652e2c 100644 --- a/src/common/src/array/num256_array.rs +++ b/src/common/src/array/num256_array.rs @@ -21,7 +21,7 @@ use risingwave_pb::common::Buffer; use risingwave_pb::data::PbArray; use crate::array::{Array, ArrayBuilder, ArrayImpl, ArrayResult}; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::types::{DataType, Int256, Int256Ref, Scalar}; #[derive(Debug, Clone, EstimateSize)] diff --git a/src/common/src/array/primitive_array.rs b/src/common/src/array/primitive_array.rs index 9840ba68135c5..9ae7912e9887e 100644 --- a/src/common/src/array/primitive_array.rs +++ b/src/common/src/array/primitive_array.rs @@ -22,7 +22,7 @@ use risingwave_pb::common::Buffer; use risingwave_pb::data::{ArrayType, PbArray}; use super::{Array, ArrayBuilder, ArrayImpl, ArrayResult}; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::for_all_native_types; use crate::types::*; @@ -315,7 +315,6 @@ impl ArrayBuilder for PrimitiveArrayBuilder { #[cfg(test)] mod tests { use super::*; - use crate::types::{F32, F64}; fn helper_test_builder(data: Vec>) -> PrimitiveArray { let mut builder = PrimitiveArrayBuilder::::new(data.len()); diff --git a/src/common/src/array/proto_reader.rs b/src/common/src/array/proto_reader.rs index b0b133d1b2df0..073ad0b3de7ba 100644 --- a/src/common/src/array/proto_reader.rs +++ b/src/common/src/array/proto_reader.rs @@ -17,16 +17,10 @@ use std::io::{Cursor, Read}; use anyhow::Context; use byteorder::{BigEndian, ReadBytesExt}; use paste::paste; -use risingwave_pb::data::{PbArray, PbArrayType}; +use risingwave_pb::data::PbArrayType; use super::*; use crate::array::value_reader::{PrimitiveValueReader, VarSizedValueReader}; -use crate::array::{ - Array, ArrayBuilder, ArrayImpl, ArrayResult, BoolArray, DateArrayBuilder, IntervalArrayBuilder, - PrimitiveArrayBuilder, PrimitiveArrayItemType, TimeArrayBuilder, TimestampArrayBuilder, -}; -use crate::buffer::Bitmap; -use crate::types::{Date, Interval, Time, Timestamp}; impl ArrayImpl { pub fn from_protobuf(array: &PbArray, cardinality: usize) -> ArrayResult { @@ -252,12 +246,6 @@ fn read_string_array>( #[cfg(test)] mod tests { use super::*; - use crate::array::{ - Array, ArrayBuilder, BoolArray, BoolArrayBuilder, DateArray, DateArrayBuilder, - DecimalArray, DecimalArrayBuilder, I32Array, I32ArrayBuilder, TimeArray, TimeArrayBuilder, - TimestampArray, TimestampArrayBuilder, Utf8Array, Utf8ArrayBuilder, - }; - use crate::types::{Date, Decimal, Time, Timestamp}; // Convert a column to protobuf, then convert it back to column, and ensures the two are // identical. diff --git a/src/common/src/array/stream_chunk.rs b/src/common/src/array/stream_chunk.rs index 157e98a429544..917d484458518 100644 --- a/src/common/src/array/stream_chunk.rs +++ b/src/common/src/array/stream_chunk.rs @@ -19,6 +19,7 @@ use std::sync::Arc; use std::{fmt, mem}; use either::Either; +use enum_as_inner::EnumAsInner; use itertools::Itertools; use rand::prelude::SmallRng; use rand::{Rng, SeedableRng}; @@ -28,7 +29,7 @@ use risingwave_pb::data::{PbOp, PbStreamChunk}; use super::stream_chunk_builder::StreamChunkBuilder; use super::{ArrayImpl, ArrayRef, ArrayResult, DataChunkTestExt, RowRef}; use crate::array::DataChunk; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::catalog::Schema; use crate::field_generator::VarcharProperty; use crate::row::Row; @@ -40,7 +41,7 @@ use crate::types::{DataType, DefaultOrdered, ToText}; /// but always appear in pairs to represent an update operation. /// For example, table source, aggregation and outer join can generate updates by themselves, /// while most of the other operators only pass through updates with best effort. -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, EnumAsInner)] pub enum Op { Insert, Delete, @@ -79,6 +80,15 @@ impl Op { Op::UpdateInsert => Op::Insert, } } + + pub fn to_i16(self) -> i16 { + match self { + Op::Insert => 1, + Op::Delete => 2, + Op::UpdateInsert => 3, + Op::UpdateDelete => 4, + } + } } pub type Ops<'a> = &'a [Op]; @@ -132,11 +142,7 @@ impl StreamChunk { /// Should prefer using [`StreamChunkBuilder`] instead to avoid unnecessary /// allocation of rows. pub fn from_rows(rows: &[(Op, impl Row)], data_types: &[DataType]) -> Self { - // `append_row` will cause the builder to finish immediately once capacity is met. - // Hence, we allocate an extra row here, to avoid the builder finishing prematurely. - // This just makes the code cleaner, since we can loop through all rows, and consume it finally. - // TODO: introduce `new_unlimited` to decouple memory reservation from builder capacity. - let mut builder = StreamChunkBuilder::new(rows.len() + 1, data_types.to_vec()); + let mut builder = StreamChunkBuilder::unlimited(data_types.to_vec(), Some(rows.len())); for (op, row) in rows { let none = builder.append_row(*op, row); @@ -658,11 +664,7 @@ impl StreamChunk { let data_types = chunks[0].data_types(); let size = chunks.iter().map(|c| c.cardinality()).sum::(); - // `append_row` will cause the builder to finish immediately once capacity is met. - // Hence, we allocate an extra row here, to avoid the builder finishing prematurely. - // This just makes the code cleaner, since we can loop through all rows, and consume it finally. - // TODO: introduce `new_unlimited` to decouple memory reservation from builder capacity. - let mut builder = StreamChunkBuilder::new(size + 1, data_types); + let mut builder = StreamChunkBuilder::unlimited(data_types, Some(size)); for chunk in chunks { // TODO: directly append chunks. diff --git a/src/common/src/array/stream_chunk_builder.rs b/src/common/src/array/stream_chunk_builder.rs index 2d70c925d696f..5fd6ae8c761c1 100644 --- a/src/common/src/array/stream_chunk_builder.rs +++ b/src/common/src/array/stream_chunk_builder.rs @@ -14,7 +14,7 @@ use crate::array::stream_record::Record; use crate::array::{ArrayBuilderImpl, Op, StreamChunk}; -use crate::buffer::BitmapBuilder; +use crate::bitmap::BitmapBuilder; use crate::row::Row; use crate::types::{DataType, DatumRef}; use crate::util::iter_util::ZipEqFast; @@ -33,10 +33,15 @@ pub struct StreamChunkBuilder { /// Data types of columns data_types: Vec, - /// Maximum capacity of column builder - capacity: usize, + /// Max number of rows in a chunk. When it's `Some(n)`, the chunk builder will, if necessary, + /// yield a chunk of which the size is strictly less than or equal to `n` when appending records. + /// When it's `None`, the chunk builder will yield chunks only when `take` is called. + max_chunk_size: Option, - /// Size of column builder + /// The initial capacity of `ops` and `ArrayBuilder`s. + initial_capacity: usize, + + /// Number of currently pending rows. size: usize, } @@ -52,43 +57,58 @@ impl Drop for StreamChunkBuilder { } } +const MAX_INITIAL_CAPACITY: usize = 4096; +const DEFAULT_INITIAL_CAPACITY: usize = 64; + impl StreamChunkBuilder { - pub fn new(chunk_size: usize, data_types: Vec) -> Self { - assert!(chunk_size > 0); + /// Create a new `StreamChunkBuilder` with a fixed max chunk size. + /// Note that in the case of ending with `Update`, the builder may yield a chunk with size + /// `max_chunk_size + 1`. + pub fn new(max_chunk_size: usize, data_types: Vec) -> Self { + assert!(max_chunk_size > 0); + + let initial_capacity = max_chunk_size.min(MAX_INITIAL_CAPACITY); - let ops = Vec::with_capacity(chunk_size); + let ops = Vec::with_capacity(initial_capacity); let column_builders = data_types .iter() - .map(|datatype| datatype.create_array_builder(chunk_size)) + .map(|datatype| datatype.create_array_builder(initial_capacity)) .collect(); + let vis_builder = BitmapBuilder::with_capacity(initial_capacity); Self { ops, column_builders, data_types, - vis_builder: BitmapBuilder::default(), - capacity: chunk_size, + vis_builder, + max_chunk_size: Some(max_chunk_size), + initial_capacity, size: 0, } } - /// Increase chunk size - /// - /// A [`StreamChunk`] will be returned when `size == capacity` - #[must_use] - fn inc_size(&mut self) -> Option { - self.size += 1; - - // Take a chunk when capacity is exceeded. Splitting `UpdateDelete` and `UpdateInsert` - // should be avoided, so when the last one is `UpdateDelete`, we delay the chunk until - // `UpdateInsert` comes. This means the output chunk size may exceed the given `chunk_size`, - // and theoretically at most `chunk_size + 1` if inputs are consistent. - if self.size >= self.capacity && self.ops[self.ops.len() - 1] != Op::UpdateDelete { - self.take() - } else { - None + /// Create a new `StreamChunkBuilder` with unlimited chunk size. + /// The builder will only yield chunks when `take` is called. + pub fn unlimited(data_types: Vec, initial_capacity: Option) -> Self { + let initial_capacity = initial_capacity.unwrap_or(DEFAULT_INITIAL_CAPACITY); + Self { + ops: Vec::with_capacity(initial_capacity), + column_builders: data_types + .iter() + .map(|datatype| datatype.create_array_builder(initial_capacity)) + .collect(), + data_types, + vis_builder: BitmapBuilder::default(), + max_chunk_size: None, + initial_capacity, + size: 0, } } + /// Get the current number of rows in the builder. + pub fn size(&self) -> usize { + self.size + } + /// Append an iterator of output index and datum to the builder, return a chunk if the builder /// is full. /// @@ -102,20 +122,6 @@ impl StreamChunkBuilder { self.append_iter_inner::(op, iter) } - #[must_use] - fn append_iter_inner<'a, const VIS: bool>( - &mut self, - op: Op, - iter: impl IntoIterator)>, - ) -> Option { - self.ops.push(op); - for (i, datum) in iter { - self.column_builders[i].append(datum); - } - self.vis_builder.append(VIS); - self.inc_size() - } - /// Append a row to the builder, return a chunk if the builder is full. #[must_use] pub fn append_row(&mut self, op: Op, row: impl Row) -> Option { @@ -132,39 +138,221 @@ impl StreamChunkBuilder { #[must_use] pub fn append_record(&mut self, record: Record) -> Option { match record { - Record::Insert { new_row } => self.append_row(Op::Insert, new_row), - Record::Delete { old_row } => self.append_row(Op::Delete, old_row), + Record::Insert { new_row } => { + self.append_iter_inner::(Op::Insert, new_row.iter().enumerate()) + } + Record::Delete { old_row } => { + self.append_iter_inner::(Op::Delete, old_row.iter().enumerate()) + } Record::Update { old_row, new_row } => { - let none = self.append_row(Op::UpdateDelete, old_row); - debug_assert!(none.is_none()); - self.append_row(Op::UpdateInsert, new_row) + let none = + self.append_iter_inner::(Op::UpdateDelete, old_row.iter().enumerate()); + assert!(none.is_none()); + self.append_iter_inner::(Op::UpdateInsert, new_row.iter().enumerate()) } } } + /// Take all the pending data and return a chunk. If there is no pending data, return `None`. + /// Note that if this is an unlimited chunk builder, the only way to get a chunk is to call + /// `take`. #[must_use] pub fn take(&mut self) -> Option { if self.size == 0 { return None; } - self.size = 0; - let new_columns = self + + let ops = std::mem::replace(&mut self.ops, Vec::with_capacity(self.initial_capacity)); + let columns = self .column_builders .iter_mut() .zip_eq_fast(&self.data_types) .map(|(builder, datatype)| { - std::mem::replace(builder, datatype.create_array_builder(self.capacity)).finish() + std::mem::replace( + builder, + datatype.create_array_builder(self.initial_capacity), + ) + .finish() }) .map(Into::into) .collect::>(); - let vis = std::mem::take(&mut self.vis_builder).finish(); - Some(StreamChunk::with_visibility( - std::mem::replace(&mut self.ops, Vec::with_capacity(self.capacity)), - new_columns, - vis, - )) + Some(StreamChunk::with_visibility(ops, columns, vis)) + } + + #[must_use] + fn append_iter_inner<'a, const VIS: bool>( + &mut self, + op: Op, + iter: impl IntoIterator)>, + ) -> Option { + self.ops.push(op); + for (i, datum) in iter { + self.column_builders[i].append(datum); + } + self.vis_builder.append(VIS); + self.size += 1; + + if let Some(max_chunk_size) = self.max_chunk_size { + if self.size == max_chunk_size && !op.is_update_delete() || self.size > max_chunk_size { + // Two situations here: + // 1. `self.size == max_chunk_size && op == Op::UpdateDelete` + // We should wait for next `UpdateInsert` to join the chunk. + // 2. `self.size > max_chunk_size` + // Here we assert that `self.size == max_chunk_size + 1`. It's possible that + // the `Op` after `UpdateDelete` is not `UpdateInsert`, if something inconsistent + // happens, we should still take the existing data. + self.take() + } else { + None + } + } else { + // unlimited + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::array::{Datum, StreamChunkTestExt}; + use crate::row::OwnedRow; + + #[test] + fn test_stream_chunk_builder() { + let row = OwnedRow::new(vec![Datum::None, Datum::None]); + let mut builder = StreamChunkBuilder::new(3, vec![DataType::Int32, DataType::Int32]); + let res = builder.append_row(Op::Delete, row.clone()); + assert!(res.is_none()); + let res = builder.append_row(Op::Insert, row.clone()); + assert!(res.is_none()); + let res = builder.append_row(Op::Insert, row.clone()); + assert_eq!( + res, + Some(StreamChunk::from_pretty( + " i i + - . . + + . . + + . ." + )) + ); + let res = builder.take(); + assert!(res.is_none()); + + let res = builder.append_row_invisible(Op::Delete, row.clone()); + assert!(res.is_none()); + let res = builder.append_iter(Op::Delete, row.clone().iter().enumerate()); + assert!(res.is_none()); + let res = builder.append_record(Record::Insert { + new_row: row.clone(), + }); + assert_eq!( + res, + Some(StreamChunk::from_pretty( + " i i + - . . D + - . . + + . ." + )) + ); + + let res = builder.append_row(Op::UpdateDelete, row.clone()); + assert!(res.is_none()); + let res = builder.append_row(Op::UpdateInsert, row.clone()); + assert!(res.is_none()); + let res = builder.append_record(Record::Update { + old_row: row.clone(), + new_row: row.clone(), + }); + assert_eq!( + res, + Some(StreamChunk::from_pretty( + " i i + U- . . + U+ . . + U- . . + U+ . ." + )) + ); + let res = builder.take(); + assert!(res.is_none()); + } + + #[test] + fn test_stream_chunk_builder_with_max_size_1() { + let row = OwnedRow::new(vec![Datum::None, Datum::None]); + let mut builder = StreamChunkBuilder::new(1, vec![DataType::Int32, DataType::Int32]); + + let res = builder.append_row(Op::Delete, row.clone()); + assert_eq!( + res, + Some(StreamChunk::from_pretty( + " i i + - . ." + )) + ); + let res = builder.append_row(Op::Insert, row.clone()); + assert_eq!( + res, + Some(StreamChunk::from_pretty( + " i i + + . ." + )) + ); + + let res = builder.append_record(Record::Update { + old_row: row.clone(), + new_row: row.clone(), + }); + assert_eq!( + res, + Some(StreamChunk::from_pretty( + " i i + U- . . + U+ . ." + )) + ); + + let res = builder.append_row(Op::UpdateDelete, row.clone()); + assert!(res.is_none()); + let res = builder.append_row(Op::UpdateDelete, row.clone()); // note this is an inconsistency + assert_eq!( + res, + Some(StreamChunk::from_pretty( + " i i + U- . . + U- . ." + )) + ); + } + + #[test] + fn test_unlimited_stream_chunk_builder() { + let row = OwnedRow::new(vec![Datum::None, Datum::None]); + let mut builder = + StreamChunkBuilder::unlimited(vec![DataType::Int32, DataType::Int32], None); + + let res = builder.append_row(Op::Delete, row.clone()); + assert!(res.is_none()); + let res = builder.append_row(Op::Insert, row.clone()); + assert!(res.is_none()); + let res = builder.append_row(Op::UpdateDelete, row.clone()); + assert!(res.is_none()); + let res = builder.append_row(Op::UpdateInsert, row.clone()); + assert!(res.is_none()); + + for _ in 0..2048 { + let res = builder.append_record(Record::Update { + old_row: row.clone(), + new_row: row.clone(), + }); + assert!(res.is_none()); + } + + let res = builder.take(); + assert_eq!(res.unwrap().capacity(), 2048 * 2 + 4); } } diff --git a/src/common/src/array/struct_array.rs b/src/common/src/array/struct_array.rs index e5a1687bbcfeb..22aae00c84f4c 100644 --- a/src/common/src/array/struct_array.rs +++ b/src/common/src/array/struct_array.rs @@ -25,7 +25,7 @@ use risingwave_pb::data::{PbArray, PbArrayType, StructArrayData}; use super::{Array, ArrayBuilder, ArrayBuilderImpl, ArrayImpl, ArrayResult, DataChunk}; use crate::array::ArrayRef; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::error::BoxedError; use crate::types::{ hash_datum, DataType, Datum, DatumRef, DefaultOrd, Scalar, ScalarImpl, StructType, ToDatumRef, diff --git a/src/common/src/array/utf8_array.rs b/src/common/src/array/utf8_array.rs index eddcc50bb8ec2..8463f73e3ee58 100644 --- a/src/common/src/array/utf8_array.rs +++ b/src/common/src/array/utf8_array.rs @@ -19,7 +19,7 @@ use risingwave_pb::data::{ArrayType, PbArray}; use super::bytes_array::{BytesWriter, PartialBytesWriter}; use super::{Array, ArrayBuilder, BytesArray, BytesArrayBuilder, DataType}; -use crate::buffer::Bitmap; +use crate::bitmap::Bitmap; /// `Utf8Array` is a collection of Rust Utf8 `str`s. It's a wrapper of `BytesArray`. #[derive(Debug, Clone, PartialEq, Eq)] @@ -112,10 +112,6 @@ impl Utf8Array { } builder.finish() } - - pub(super) fn data(&self) -> &[u8] { - self.bytes.data() - } } /// `Utf8ArrayBuilder` use `&str` to build an `Utf8Array`. @@ -127,15 +123,20 @@ pub struct Utf8ArrayBuilder { impl ArrayBuilder for Utf8ArrayBuilder { type ArrayType = Utf8Array; - fn new(capacity: usize) -> Self { + /// Creates a new `Utf8ArrayBuilder`. + /// + /// `item_capacity` is the number of items to pre-allocate. The size of the preallocated + /// buffer of offsets is the number of items plus one. + /// No additional memory is pre-allocated for the data buffer. + fn new(item_capacity: usize) -> Self { Self { - bytes: BytesArrayBuilder::new(capacity), + bytes: BytesArrayBuilder::new(item_capacity), } } - fn with_type(capacity: usize, ty: DataType) -> Self { + fn with_type(item_capacity: usize, ty: DataType) -> Self { assert_eq!(ty, DataType::Varchar); - Self::new(capacity) + Self::new(item_capacity) } #[inline] @@ -170,6 +171,17 @@ impl Utf8ArrayBuilder { bytes: self.bytes.writer(), } } + + /// Append an element as the `Display` format to the array. + pub fn append_display(&mut self, value: Option) { + if let Some(s) = value { + let mut writer = self.writer().begin(); + write!(writer, "{}", s).unwrap(); + writer.finish(); + } else { + self.append_null(); + } + } } pub struct StringWriter<'a> { @@ -297,12 +309,6 @@ mod tests { let array = Utf8Array::from_iter(&input); assert_eq!(array.len(), input.len()); - - assert_eq!( - array.bytes.data().len(), - input.iter().map(|s| s.unwrap_or("").len()).sum::() - ); - assert_eq!(input, array.iter().collect_vec()); } diff --git a/src/common/src/buffer/bitmap.rs b/src/common/src/bitmap.rs similarity index 94% rename from src/common/src/buffer/bitmap.rs rename to src/common/src/bitmap.rs index 272cae6404f1e..7ef6bf039f47d 100644 --- a/src/common/src/buffer/bitmap.rs +++ b/src/common/src/bitmap.rs @@ -37,7 +37,7 @@ #![allow(clippy::disallowed_methods)] use std::iter::{self, TrustedLen}; -use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, Not, RangeInclusive}; +use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, Not, Range, RangeInclusive}; use risingwave_common_estimate_size::EstimateSize; use risingwave_pb::common::buffer::CompressionType; @@ -318,6 +318,11 @@ impl Bitmap { self.count_ones } + /// Returns true if any bit is set to 1. + pub fn any(&self) -> bool { + self.count_ones != 0 + } + /// Returns the length of vector to store `num_bits` bits. fn vec_len(num_bits: usize) -> usize { (num_bits + BITS - 1) / BITS @@ -415,6 +420,46 @@ impl Bitmap { self.count_ones ) } + + /// Creates a new bitmap with all bits in range set to 1. + /// + /// # Example + /// ``` + /// use risingwave_common::bitmap::Bitmap; + /// let bitmap = Bitmap::from_range(200, 100..180); + /// assert_eq!(bitmap.count_ones(), 80); + /// for i in 0..200 { + /// assert_eq!(bitmap.is_set(i), i >= 100 && i < 180); + /// } + /// ``` + pub fn from_range(num_bits: usize, range: Range) -> Self { + assert!(range.start <= range.end); + assert!(range.end <= num_bits); + if range.start == range.end { + return Self::zeros(num_bits); + } else if range == (0..num_bits) { + return Self::ones(num_bits); + } + let mut bits = vec![0; Self::vec_len(num_bits)]; + let start = range.start / BITS; + let end = range.end / BITS; + let start_offset = range.start % BITS; + let end_offset = range.end % BITS; + if start == end { + bits[start] = ((1 << (end_offset - start_offset)) - 1) << start_offset; + } else { + bits[start] = !0 << start_offset; + bits[start + 1..end].fill(!0); + if end_offset != 0 { + bits[end] = (1 << end_offset) - 1; + } + } + Self { + bits: Some(bits.into()), + num_bits, + count_ones: range.len(), + } + } } impl From for Bitmap { @@ -763,6 +808,16 @@ impl<'a> iter::Iterator for BitmapOnesIter<'a> { } } } + + fn size_hint(&self) -> (usize, Option) { + match self { + BitmapOnesIter::Range { start, end } => { + let remaining = end - start; + (remaining, Some(remaining)) + } + BitmapOnesIter::Buffer { bits, .. } => (0, Some(bits.len() * usize::BITS as usize)), + } + } } #[cfg(test)] diff --git a/src/common/src/buffer/mod.rs b/src/common/src/buffer/mod.rs deleted file mode 100644 index 7628f6eddceb4..0000000000000 --- a/src/common/src/buffer/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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. - -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you 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 bitmap; -pub use bitmap::*; diff --git a/src/common/src/cache.rs b/src/common/src/cache.rs index 99a373d6a94a8..e86ef432eea95 100644 --- a/src/common/src/cache.rs +++ b/src/common/src/cache.rs @@ -1019,14 +1019,12 @@ impl Clone for CacheableEntry { #[cfg(test)] mod tests { use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; + use std::hash::Hasher; use std::pin::Pin; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; - use std::sync::Arc; use std::task::{Context, Poll}; - use futures::FutureExt; use rand::rngs::SmallRng; use rand::{RngCore, SeedableRng}; use tokio::sync::oneshot::error::TryRecvError; @@ -1035,6 +1033,7 @@ mod tests { pub struct Block { pub offset: u64, + #[allow(dead_code)] pub sst: u64, } diff --git a/src/common/src/catalog/column.rs b/src/common/src/catalog/column.rs index 82d2f22f41cb4..b3065defea2a2 100644 --- a/src/common/src/catalog/column.rs +++ b/src/common/src/catalog/column.rs @@ -21,7 +21,7 @@ use risingwave_pb::plan_common::{ AdditionalColumn, ColumnDescVersion, PbColumnCatalog, PbColumnDesc, }; -use super::row_id_column_desc; +use super::{row_id_column_desc, USER_COLUMN_ID_OFFSET}; use crate::catalog::{cdc_table_name_column_desc, offset_column_desc, Field, ROW_ID_COLUMN_ID}; use crate::types::DataType; @@ -45,6 +45,10 @@ impl ColumnId { pub const fn placeholder() -> Self { Self(i32::MAX - 1) } + + pub const fn first_user_column() -> Self { + Self(USER_COLUMN_ID_OFFSET) + } } impl ColumnId { @@ -320,6 +324,20 @@ pub struct ColumnCatalog { } impl ColumnCatalog { + pub fn visible(column_desc: ColumnDesc) -> Self { + Self { + column_desc, + is_hidden: false, + } + } + + pub fn hidden(column_desc: ColumnDesc) -> Self { + Self { + column_desc, + is_hidden: true, + } + } + /// Get the column catalog's is hidden. pub fn is_hidden(&self) -> bool { self.is_hidden @@ -346,6 +364,11 @@ impl ColumnCatalog { self.column_desc.is_default() } + /// If the columns is an `INCLUDE ... AS ...` connector column. + pub fn is_connector_additional_column(&self) -> bool { + self.column_desc.additional_column.column_type.is_some() + } + /// Get a reference to the column desc's data type. pub fn data_type(&self) -> &DataType { &self.column_desc.data_type @@ -430,15 +453,30 @@ pub fn columns_extend(preserved_columns: &mut Vec, columns: Vec bool { - let mut column_ids = columns +pub fn debug_assert_column_ids_distinct(columns: &[ColumnCatalog]) { + debug_assert!( + columns + .iter() + .map(|c| c.column_id()) + .duplicates() + .next() + .is_none(), + "duplicate ColumnId found in source catalog. Columns: {columns:#?}" + ); +} + +/// FIXME: perhapts we should use sth like `ColumnIdGenerator::new_alter`, +/// However, the `SourceVersion` is problematic: It doesn't contain `next_col_id`. +/// (But for now this isn't a large problem, since drop column is not allowed for source yet..) +/// +/// Besides, the logic of column id handling is a mess. +/// In some places, we use `ColumnId::placeholder()`, and use `col_id_gen` to fill it at the end; +/// In other places, we create column id ad-hoc. +pub fn max_column_id(columns: &[ColumnCatalog]) -> ColumnId { + // XXX: should we check the column IDs of struct fields here? + columns .iter() - .map(|column| column.column_id().get_id()) - .collect_vec(); - column_ids.sort(); - let original_len = column_ids.len(); - column_ids.dedup(); - column_ids.len() == original_len + .fold(ColumnId::first_user_column(), |a, b| a.max(b.column_id())) } #[cfg(test)] diff --git a/src/common/src/catalog/external_table.rs b/src/common/src/catalog/external_table.rs index 3de38a29a499c..797d789b85fc0 100644 --- a/src/common/src/catalog/external_table.rs +++ b/src/common/src/catalog/external_table.rs @@ -40,8 +40,6 @@ pub struct CdcTableDesc { /// Column indices for primary keys. pub stream_key: Vec, - pub value_indices: Vec, - /// properties will be passed into the `StreamScanNode` pub connect_properties: BTreeMap, } @@ -67,6 +65,7 @@ impl CdcTableDesc { table_name: self.external_table_name.clone(), stream_key: self.stream_key.iter().map(|k| *k as _).collect(), connect_properties: self.connect_properties.clone(), + secret_refs: Default::default(), } } diff --git a/src/common/src/catalog/internal_table.rs b/src/common/src/catalog/internal_table.rs index 72e377b04d417..ba6370cdfc634 100644 --- a/src/common/src/catalog/internal_table.rs +++ b/src/common/src/catalog/internal_table.rs @@ -23,14 +23,14 @@ use regex::Regex; pub const RW_INTERNAL_TABLE_FUNCTION_NAME: &str = "rw_table"; pub fn generate_internal_table_name_with_type( - mview_name: &str, + job_name: &str, fragment_id: u32, table_id: u32, table_type: &str, ) -> String { format!( "__internal_{}_{}_{}_{}", - mview_name, + job_name, fragment_id, table_type.to_lowercase(), table_id @@ -43,13 +43,6 @@ pub fn valid_table_name(table_name: &str) -> bool { !INTERNAL_TABLE_NAME.is_match(table_name) } -pub fn is_subscription_internal_table(subscription_name: &str, table_name: &str) -> bool { - let regex = - Regex::new(format!(r"__internal_{}_(\d+)_subscription_(\d+)", subscription_name).as_str()) - .unwrap(); - regex.is_match(table_name) -} - pub fn get_dist_key_in_pk_indices>( dist_key_indices: &[I], pk_indices: &[I], diff --git a/src/common/src/catalog/mod.rs b/src/common/src/catalog/mod.rs index 1909ca1635f7c..86c6e8895c066 100644 --- a/src/common/src/catalog/mod.rs +++ b/src/common/src/catalog/mod.rs @@ -33,6 +33,7 @@ use risingwave_pb::catalog::{ }; use risingwave_pb::plan_common::ColumnDescVersion; pub use schema::{test_utils as schema_test_utils, Field, FieldDisplay, Schema}; +use serde::{Deserialize, Serialize}; use crate::array::DataChunk; pub use crate::constants::hummock; @@ -228,7 +229,20 @@ impl From for u32 { } } -#[derive(Clone, Copy, Debug, Display, Default, Hash, PartialOrd, PartialEq, Eq, Ord)] +#[derive( + Clone, + Copy, + Debug, + Display, + Default, + Hash, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, +)] #[display("{table_id}")] pub struct TableId { pub table_id: u32, @@ -437,6 +451,41 @@ impl From for u32 { } } +#[derive(Clone, Copy, Debug, Display, Default, Hash, PartialOrd, PartialEq, Eq, Ord)] +pub struct SecretId(pub u32); + +impl SecretId { + pub const fn new(id: u32) -> Self { + SecretId(id) + } + + pub const fn placeholder() -> Self { + SecretId(OBJECT_ID_PLACEHOLDER) + } + + pub fn secret_id(&self) -> u32 { + self.0 + } +} + +impl From for SecretId { + fn from(id: u32) -> Self { + Self::new(id) + } +} + +impl From<&u32> for SecretId { + fn from(id: &u32) -> Self { + Self::new(*id) + } +} + +impl From for u32 { + fn from(id: SecretId) -> Self { + id.0 + } +} + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ConflictBehavior { #[default] diff --git a/src/common/src/config.rs b/src/common/src/config.rs index e1e8ad8cfcd87..47d53a75b76a1 100644 --- a/src/common/src/config.rs +++ b/src/common/src/config.rs @@ -24,7 +24,7 @@ use std::num::NonZeroUsize; use anyhow::Context; use clap::ValueEnum; use educe::Educe; -use foyer::memory::{LfuConfig, LruConfig, S3FifoConfig}; +use foyer::{LfuConfig, LruConfig, S3FifoConfig}; use risingwave_common_proc_macro::ConfigDoc; pub use risingwave_common_proc_macro::OverrideConfig; use risingwave_pb::meta::SystemParams; @@ -175,7 +175,10 @@ pub enum MetaBackend { #[default] Mem, Etcd, - Sql, + Sql, // keep for backward compatibility + Sqlite, + Postgres, + Mysql, } /// The section `[meta]` in `risingwave.toml`. @@ -309,9 +312,12 @@ pub struct MetaConfig { #[serde(default)] pub do_not_config_object_storage_lifecycle: bool, + /// Count of partition in split group. Meta will assign this value to every new group when it splits from default-group by automatically. + /// Each partition contains aligned data of `VirtualNode::COUNT / partition_vnode_count` consecutive virtual-nodes of one state table. #[serde(default = "default::meta::partition_vnode_count")] pub partition_vnode_count: u32, + /// The threshold of write throughput to trigger a group split. Increase this configuration value to avoid split too many groups with few data write. #[serde(default = "default::meta::table_write_throughput_threshold")] pub table_write_throughput_threshold: u64, @@ -334,8 +340,23 @@ pub struct MetaConfig { #[config_doc(nested)] pub compaction_config: CompactionConfig, - #[serde(default = "default::meta::hybird_partition_vnode_count")] - pub hybird_partition_vnode_count: u32, + /// Count of partitions of tables in default group and materialized view group. + /// The meta node will decide according to some strategy whether to cut the boundaries of the file according to the vnode alignment. + /// Each partition contains aligned data of `VirtualNode::COUNT / hybrid_partition_vnode_count` consecutive virtual-nodes of one state table. + /// Set it zero to disable this feature. + #[serde(default = "default::meta::hybrid_partition_vnode_count")] + pub hybrid_partition_vnode_count: u32, + + /// The threshold of table size in one compact task to decide whether to partition one table into `hybrid_partition_vnode_count` parts, which belongs to default group and materialized view group. + /// Set it max value of 64-bit number to disable this feature. + #[serde(default = "default::meta::compact_task_table_size_partition_threshold_low")] + pub compact_task_table_size_partition_threshold_low: u64, + + /// The threshold of table size in one compact task to decide whether to partition one table into `partition_vnode_count` parts, which belongs to default group and materialized view group. + /// Set it max value of 64-bit number to disable this feature. + #[serde(default = "default::meta::compact_task_table_size_partition_threshold_high")] + pub compact_task_table_size_partition_threshold_high: u64, + #[serde(default = "default::meta::event_log_enabled")] pub event_log_enabled: bool, /// Keeps the latest N events per channel. @@ -348,6 +369,9 @@ pub struct MetaConfig { /// Whether compactor should rewrite row to remove dropped column. #[serde(default = "default::meta::enable_dropped_column_reclaim")] pub enable_dropped_column_reclaim: bool, + + #[serde(default = "default::meta::secret_store_private_key")] + pub secret_store_private_key: Vec, } #[derive(Copy, Clone, Debug, Default)] @@ -434,6 +458,11 @@ pub struct MetaDeveloperConfig { pub enable_trivial_move: bool, #[serde(default = "default::developer::enable_check_task_level_overlap")] pub enable_check_task_level_overlap: bool, + #[serde(default = "default::developer::max_trivial_move_task_count_per_loop")] + pub max_trivial_move_task_count_per_loop: usize, + + #[serde(default = "default::developer::max_get_task_probe_times")] + pub max_get_task_probe_times: usize, } /// The section `[server]` in `risingwave.toml`. @@ -504,6 +533,15 @@ pub struct BatchConfig { /// This is the secs used to mask a worker unavailable temporarily. #[serde(default = "default::batch::mask_worker_temporary_secs")] pub mask_worker_temporary_secs: usize, + + /// Keywords on which SQL option redaction is based in the query log. + /// A SQL option with a name containing any of these keywords will be redacted. + #[serde(default = "default::batch::redact_sql_option_keywords")] + pub redact_sql_option_keywords: Vec, + + /// Enable the spill out to disk feature for batch queries. + #[serde(default = "default::batch::enable_spill")] + pub enable_spill: bool, } /// The section `[streaming]` in `risingwave.toml`. @@ -578,6 +616,8 @@ pub enum CacheEvictionConfig { }, S3Fifo { small_queue_capacity_ratio_in_percent: Option, + ghost_queue_capacity_ratio_in_percent: Option, + small_to_main_freq_threshold: Option, }, } @@ -611,8 +651,14 @@ pub struct StorageConfig { #[serde(default = "default::storage::shared_buffer_flush_ratio")] pub shared_buffer_flush_ratio: f32, + /// The minimum total flush size of shared buffer spill. When a shared buffer spilled is trigger, + /// the total flush size across multiple epochs should be at least higher than this size. + #[serde(default = "default::storage::shared_buffer_min_batch_flush_size_mb")] + pub shared_buffer_min_batch_flush_size_mb: usize, + /// The threshold for the number of immutable memtables to merge to a new imm. #[serde(default = "default::storage::imm_merge_threshold")] + #[deprecated] pub imm_merge_threshold: usize, /// Whether to enable write conflict detection @@ -667,6 +713,9 @@ pub struct StorageConfig { #[serde(default = "default::storage::sstable_id_remote_fetch_number")] pub sstable_id_remote_fetch_number: u32, + #[serde(default = "default::storage::min_sstable_size_mb")] + pub min_sstable_size_mb: u32, + #[serde(default)] pub data_file_cache: FileCacheConfig, @@ -680,10 +729,6 @@ pub struct StorageConfig { #[serde(default = "default::storage::min_sst_size_for_streaming_upload")] pub min_sst_size_for_streaming_upload: u64, - /// Max sub compaction task numbers - #[serde(default = "default::storage::max_sub_compaction")] - pub max_sub_compaction: u32, - #[serde(default = "default::storage::max_concurrent_compaction_task_number")] pub max_concurrent_compaction_task_number: u64, @@ -713,6 +758,10 @@ pub struct StorageConfig { #[serde(default = "default::storage::compactor_iter_max_io_retry_times")] pub compactor_iter_max_io_retry_times: usize, + /// The window size of table info statistic history. + #[serde(default = "default::storage::table_info_statistic_history_times")] + pub table_info_statistic_history_times: usize, + #[serde(default, flatten)] #[config_doc(omitted)] pub unrecognized: Unrecognized, @@ -721,6 +770,14 @@ pub struct StorageConfig { #[serde(default = "default::storage::mem_table_spill_threshold")] pub mem_table_spill_threshold: usize, + /// The concurrent uploading number of `SSTables` of buidler + #[serde(default = "default::storage::compactor_concurrent_uploading_sst_count")] + pub compactor_concurrent_uploading_sst_count: Option, + + /// Object storage configuration + /// 1. General configuration + /// 2. Some special configuration of Backend + /// 3. Retry and timeout configuration #[serde(default)] pub object_store: ObjectStoreConfig, } @@ -776,12 +833,6 @@ pub struct FileCacheConfig { #[serde(default = "default::file_cache::file_capacity_mb")] pub file_capacity_mb: usize, - #[serde(default = "default::file_cache::device_align")] - pub device_align: usize, - - #[serde(default = "default::file_cache::device_io_size")] - pub device_io_size: usize, - #[serde(default = "default::file_cache::flushers")] pub flushers: usize, @@ -791,17 +842,11 @@ pub struct FileCacheConfig { #[serde(default = "default::file_cache::recover_concurrency")] pub recover_concurrency: usize, - #[serde(default = "default::file_cache::lfu_window_to_cache_size_ratio")] - pub lfu_window_to_cache_size_ratio: usize, - - #[serde(default = "default::file_cache::lfu_tiny_lru_capacity_ratio")] - pub lfu_tiny_lru_capacity_ratio: f64, - #[serde(default = "default::file_cache::insert_rate_limit_mb")] pub insert_rate_limit_mb: usize, - #[serde(default = "default::file_cache::catalog_bits")] - pub catalog_bits: usize, + #[serde(default = "default::file_cache::indexer_shards")] + pub indexer_shards: usize, #[serde(default = "default::file_cache::compression")] pub compression: String, @@ -922,11 +967,37 @@ pub struct StreamingDeveloperConfig { #[serde(default = "default::developer::memory_controller_threshold_stable")] pub memory_controller_threshold_stable: f64, + #[serde(default = "default::developer::memory_controller_eviction_factor_aggressive")] + pub memory_controller_eviction_factor_aggressive: f64, + + #[serde(default = "default::developer::memory_controller_eviction_factor_graceful")] + pub memory_controller_eviction_factor_graceful: f64, + + #[serde(default = "default::developer::memory_controller_eviction_factor_stable")] + pub memory_controller_eviction_factor_stable: f64, + + #[serde(default = "default::developer::memory_controller_sequence_tls_step")] + pub memory_controller_sequence_tls_step: u64, + + #[serde(default = "default::developer::memory_controller_sequence_tls_lag")] + pub memory_controller_sequence_tls_lag: u64, + #[serde(default = "default::developer::stream_enable_arrangement_backfill")] /// Enable arrangement backfill - /// If true, the arrangement backfill will be disabled, + /// If false, the arrangement backfill will be disabled, /// even if session variable set. + /// If true, it will be enabled by default, but session variable + /// can override it. pub enable_arrangement_backfill: bool, + + #[serde(default = "default::developer::stream_high_join_amplification_threshold")] + /// If number of hash join matches exceeds this threshold number, + /// it will be logged. + pub high_join_amplification_threshold: usize, + + /// Actor tokio metrics is enabled if `enable_actor_tokio_metrics` is set or metrics level >= Debug. + #[serde(default = "default::developer::enable_actor_tokio_metrics")] + pub enable_actor_tokio_metrics: bool, } /// The subsections `[batch.developer]`. @@ -971,48 +1042,64 @@ for_all_params!(define_system_config); /// The subsections `[storage.object_store]`. #[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde)] pub struct ObjectStoreConfig { - #[serde(default = "default::object_store_config::object_store_streaming_read_timeout_ms")] - pub object_store_streaming_read_timeout_ms: u64, - #[serde(default = "default::object_store_config::object_store_streaming_upload_timeout_ms")] - pub object_store_streaming_upload_timeout_ms: u64, - #[serde(default = "default::object_store_config::object_store_upload_timeout_ms")] - pub object_store_upload_timeout_ms: u64, - #[serde(default = "default::object_store_config::object_store_read_timeout_ms")] - pub object_store_read_timeout_ms: u64, - #[serde(default = "default::object_store_config::object_store_set_atomic_write_dir")] - pub object_store_set_atomic_write_dir: bool, + // alias is for backward compatibility + #[serde( + default = "default::object_store_config::set_atomic_write_dir", + alias = "object_store_set_atomic_write_dir" + )] + pub set_atomic_write_dir: bool, + /// Retry and timeout configuration + /// Description retry strategy driven by exponential back-off + /// Exposes the timeout and retries of each Object store interface. Therefore, the total timeout for each interface is determined based on the interface's timeout/retry configuration and the exponential back-off policy. + #[serde(default)] + pub retry: ObjectStoreRetryConfig, + + /// Some special configuration of S3 Backend #[serde(default)] pub s3: S3ObjectStoreConfig, + + // TODO: the following field will be deprecated after opendal is stablized + #[serde(default = "default::object_store_config::opendal_upload_concurrency")] + pub opendal_upload_concurrency: usize, + + // TODO: the following field will be deprecated after opendal is stablized + #[serde(default)] + pub opendal_writer_abort_on_err: bool, } impl ObjectStoreConfig { pub fn set_atomic_write_dir(&mut self) { - self.object_store_set_atomic_write_dir = true; + self.set_atomic_write_dir = true; } } /// The subsections `[storage.object_store.s3]`. #[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde)] pub struct S3ObjectStoreConfig { - #[serde(default = "default::object_store_config::s3::object_store_keepalive_ms")] - pub object_store_keepalive_ms: Option, - #[serde(default = "default::object_store_config::s3::object_store_recv_buffer_size")] - pub object_store_recv_buffer_size: Option, - #[serde(default = "default::object_store_config::s3::object_store_send_buffer_size")] - pub object_store_send_buffer_size: Option, - #[serde(default = "default::object_store_config::s3::object_store_nodelay")] - pub object_store_nodelay: Option, - #[serde(default = "default::object_store_config::s3::object_store_req_retry_interval_ms")] - pub object_store_req_retry_interval_ms: u64, - #[serde(default = "default::object_store_config::s3::object_store_req_retry_max_delay_ms")] - pub object_store_req_retry_max_delay_ms: u64, - #[serde(default = "default::object_store_config::s3::object_store_req_retry_max_attempts")] - pub object_store_req_retry_max_attempts: usize, - /// For backwards compatibility, users should use `S3ObjectStoreDeveloperConfig` instead. + // alias is for backward compatibility + #[serde( + default = "default::object_store_config::s3::keepalive_ms", + alias = "object_store_keepalive_ms" + )] + pub keepalive_ms: Option, + #[serde( + default = "default::object_store_config::s3::recv_buffer_size", + alias = "object_store_recv_buffer_size" + )] + pub recv_buffer_size: Option, #[serde( - default = "default::object_store_config::s3::developer::object_store_retry_unknown_service_error" + default = "default::object_store_config::s3::send_buffer_size", + alias = "object_store_send_buffer_size" )] + pub send_buffer_size: Option, + #[serde( + default = "default::object_store_config::s3::nodelay", + alias = "object_store_nodelay" + )] + pub nodelay: Option, + /// For backwards compatibility, users should use `S3ObjectStoreDeveloperConfig` instead. + #[serde(default = "default::object_store_config::s3::developer::retry_unknown_service_error")] pub retry_unknown_service_error: bool, #[serde(default = "default::object_store_config::s3::identity_resolution_timeout_s")] pub identity_resolution_timeout_s: u64, @@ -1025,20 +1112,112 @@ pub struct S3ObjectStoreConfig { pub struct S3ObjectStoreDeveloperConfig { /// Whether to retry s3 sdk error from which no error metadata is provided. #[serde( - default = "default::object_store_config::s3::developer::object_store_retry_unknown_service_error" + default = "default::object_store_config::s3::developer::retry_unknown_service_error", + alias = "object_store_retry_unknown_service_error" )] - pub object_store_retry_unknown_service_error: bool, + pub retry_unknown_service_error: bool, /// An array of error codes that should be retried. /// e.g. `["SlowDown", "TooManyRequests"]` #[serde( - default = "default::object_store_config::s3::developer::object_store_retryable_service_error_codes" + default = "default::object_store_config::s3::developer::retryable_service_error_codes", + alias = "object_store_retryable_service_error_codes" )] - pub object_store_retryable_service_error_codes: Vec, + pub retryable_service_error_codes: Vec, + // TODO: the following field will be deprecated after opendal is stablized #[serde(default = "default::object_store_config::s3::developer::use_opendal")] pub use_opendal: bool, } +#[derive(Clone, Debug, Serialize, Deserialize, DefaultFromSerde)] +pub struct ObjectStoreRetryConfig { + // A retry strategy driven by exponential back-off. + // The retry strategy is used for all object store operations. + /// Given a base duration for retry strategy in milliseconds. + #[serde(default = "default::object_store_config::object_store_req_backoff_interval_ms")] + pub req_backoff_interval_ms: u64, + + /// The max delay interval for the retry strategy. No retry delay will be longer than this `Duration`. + #[serde(default = "default::object_store_config::object_store_req_backoff_max_delay_ms")] + pub req_backoff_max_delay_ms: u64, + + /// A multiplicative factor that will be applied to the exponential back-off retry delay. + #[serde(default = "default::object_store_config::object_store_req_backoff_factor")] + pub req_backoff_factor: u64, + + /// Maximum timeout for `upload` operation + #[serde(default = "default::object_store_config::object_store_upload_attempt_timeout_ms")] + pub upload_attempt_timeout_ms: u64, + + /// Total counts of `upload` operation retries + #[serde(default = "default::object_store_config::object_store_upload_retry_attempts")] + pub upload_retry_attempts: usize, + + /// Maximum timeout for `streaming_upload_init` and `streaming_upload` + #[serde( + default = "default::object_store_config::object_store_streaming_upload_attempt_timeout_ms" + )] + pub streaming_upload_attempt_timeout_ms: u64, + + /// Total counts of `streaming_upload` operation retries + #[serde( + default = "default::object_store_config::object_store_streaming_upload_retry_attempts" + )] + pub streaming_upload_retry_attempts: usize, + + /// Maximum timeout for `read` operation + #[serde(default = "default::object_store_config::object_store_read_attempt_timeout_ms")] + pub read_attempt_timeout_ms: u64, + + /// Total counts of `read` operation retries + #[serde(default = "default::object_store_config::object_store_read_retry_attempts")] + pub read_retry_attempts: usize, + + /// Maximum timeout for `streaming_read_init` and `streaming_read` operation + #[serde( + default = "default::object_store_config::object_store_streaming_read_attempt_timeout_ms" + )] + pub streaming_read_attempt_timeout_ms: u64, + + /// Total counts of `streaming_read operation` retries + #[serde(default = "default::object_store_config::object_store_streaming_read_retry_attempts")] + pub streaming_read_retry_attempts: usize, + + /// Maximum timeout for `metadata` operation + #[serde(default = "default::object_store_config::object_store_metadata_attempt_timeout_ms")] + pub metadata_attempt_timeout_ms: u64, + + /// Total counts of `metadata` operation retries + #[serde(default = "default::object_store_config::object_store_metadata_retry_attempts")] + pub metadata_retry_attempts: usize, + + /// Maximum timeout for `delete` operation + #[serde(default = "default::object_store_config::object_store_delete_attempt_timeout_ms")] + pub delete_attempt_timeout_ms: u64, + + /// Total counts of `delete` operation retries + #[serde(default = "default::object_store_config::object_store_delete_retry_attempts")] + pub delete_retry_attempts: usize, + + /// Maximum timeout for `delete_object` operation + #[serde( + default = "default::object_store_config::object_store_delete_objects_attempt_timeout_ms" + )] + pub delete_objects_attempt_timeout_ms: u64, + + /// Total counts of `delete_object` operation retries + #[serde(default = "default::object_store_config::object_store_delete_objects_retry_attempts")] + pub delete_objects_retry_attempts: usize, + + /// Maximum timeout for `list` operation + #[serde(default = "default::object_store_config::object_store_list_attempt_timeout_ms")] + pub list_attempt_timeout_ms: u64, + + /// Total counts of `list` operation retries + #[serde(default = "default::object_store_config::object_store_list_retry_attempts")] + pub list_retry_attempts: usize, +} + impl SystemConfig { #![allow(deprecated)] pub fn into_init_system_params(self) -> SystemParams { @@ -1182,10 +1361,18 @@ pub mod default { 1024 * 1024 * 1024 // 1GB } - pub fn hybird_partition_vnode_count() -> u32 { + pub fn hybrid_partition_vnode_count() -> u32 { 4 } + pub fn compact_task_table_size_partition_threshold_low() -> u64 { + 128 * 1024 * 1024 // 128MB + } + + pub fn compact_task_table_size_partition_threshold_high() -> u64 { + 512 * 1024 * 1024 // 512MB + } + pub fn event_log_enabled() -> bool { true } @@ -1205,9 +1392,14 @@ pub mod default { pub fn parallelism_control_trigger_first_delay_sec() -> u64 { 30 } + pub fn enable_dropped_column_reclaim() -> bool { false } + + pub fn secret_store_private_key() -> Vec { + "demo-secret-private-key".as_bytes().to_vec() + } } pub mod server { @@ -1251,6 +1443,10 @@ pub mod default { 0.8 } + pub fn shared_buffer_min_batch_flush_size_mb() -> usize { + 800 + } + pub fn imm_merge_threshold() -> usize { 0 // disable } @@ -1287,6 +1483,14 @@ pub mod default { 10 } + pub fn ghost_queue_capacity_ratio_in_percent() -> usize { + 1000 + } + + pub fn small_to_main_freq_threshold() -> u8 { + 1 + } + pub fn meta_cache_capacity_mb() -> usize { 128 } @@ -1304,7 +1508,7 @@ pub mod default { } pub fn compactor_max_task_multiplier() -> f32 { - 2.5000 + 3.0000 } pub fn compactor_memory_available_proportion() -> f64 { @@ -1315,15 +1519,15 @@ pub mod default { 10 } + pub fn min_sstable_size_mb() -> u32 { + 32 + } + pub fn min_sst_size_for_streaming_upload() -> u64 { // 32MB 32 * 1024 * 1024 } - pub fn max_sub_compaction() -> u32 { - 4 - } - pub fn max_concurrent_compaction_task_number() -> u64 { 16 } @@ -1353,7 +1557,7 @@ pub mod default { } pub fn enable_fast_compaction() -> bool { - false + true } pub fn check_compaction_result() -> bool { @@ -1363,6 +1567,7 @@ pub mod default { pub fn max_preload_io_retry_times() -> usize { 3 } + pub fn mem_table_spill_threshold() -> usize { 4 << 20 } @@ -1378,6 +1583,14 @@ pub mod default { pub fn max_prefetch_block_number() -> usize { 16 } + + pub fn compactor_concurrent_uploading_sst_count() -> Option { + None + } + + pub fn table_info_statistic_history_times() -> usize { + 240 + } } pub mod streaming { @@ -1403,7 +1616,6 @@ pub mod default { } pub mod file_cache { - pub fn dir() -> String { "".to_string() } @@ -1416,14 +1628,6 @@ pub mod default { 64 } - pub fn device_align() -> usize { - 4096 - } - - pub fn device_io_size() -> usize { - 16 * 1024 - } - pub fn flushers() -> usize { 4 } @@ -1436,20 +1640,12 @@ pub mod default { 8 } - pub fn lfu_window_to_cache_size_ratio() -> usize { - 1 - } - - pub fn lfu_tiny_lru_capacity_ratio() -> f64 { - 0.01 - } - pub fn insert_rate_limit_mb() -> usize { 0 } - pub fn catalog_bits() -> usize { - 6 + pub fn indexer_shards() -> usize { + 64 } pub fn compression() -> String { @@ -1566,18 +1762,57 @@ pub mod default { false } + pub fn max_trivial_move_task_count_per_loop() -> usize { + 256 + } + + pub fn max_get_task_probe_times() -> usize { + 5 + } + pub fn memory_controller_threshold_aggressive() -> f64 { 0.9 } + pub fn memory_controller_threshold_graceful() -> f64 { 0.8 } + pub fn memory_controller_threshold_stable() -> f64 { 0.7 } + + pub fn memory_controller_eviction_factor_aggressive() -> f64 { + 2.0 + } + + pub fn memory_controller_eviction_factor_graceful() -> f64 { + 1.5 + } + + pub fn memory_controller_eviction_factor_stable() -> f64 { + 1.0 + } + + pub fn memory_controller_sequence_tls_step() -> u64 { + 128 + } + + pub fn memory_controller_sequence_tls_lag() -> u64 { + 32 + } + pub fn stream_enable_arrangement_backfill() -> bool { true } + + pub fn stream_high_join_amplification_threshold() -> usize { + 2048 + } + + pub fn enable_actor_tokio_metrics() -> bool { + false + } } pub use crate::system_param::default as system; @@ -1587,6 +1822,10 @@ pub mod default { false } + pub fn enable_spill() -> bool { + true + } + pub fn statement_timeout_in_sec() -> u32 { // 1 hour 60 * 60 @@ -1599,6 +1838,20 @@ pub mod default { pub fn mask_worker_temporary_secs() -> usize { 30 } + + pub fn redact_sql_option_keywords() -> Vec { + [ + "credential", + "key", + "password", + "private", + "secret", + "token", + ] + .into_iter() + .map(str::to_string) + .collect() + } } pub mod compaction_config { @@ -1608,7 +1861,8 @@ pub mod default { // decrease this configure when the generation of checkpoint barrier is not frequent. const DEFAULT_TIER_COMPACT_TRIGGER_NUMBER: u64 = 12; - const DEFAULT_TARGET_FILE_SIZE_BASE: u64 = 32 * 1024 * 1024; // 32MB + const DEFAULT_TARGET_FILE_SIZE_BASE: u64 = 32 * 1024 * 1024; + // 32MB const DEFAULT_MAX_SUB_COMPACTION: u32 = 4; const DEFAULT_LEVEL_MULTIPLIER: u64 = 5; const DEFAULT_MAX_SPACE_RECLAIM_BYTES: u64 = 512 * 1024 * 1024; // 512MB; @@ -1618,48 +1872,63 @@ pub mod default { const DEFAULT_MIN_OVERLAPPING_SUB_LEVEL_COMPACT_LEVEL_COUNT: u32 = 12; const DEFAULT_TOMBSTONE_RATIO_PERCENT: u32 = 40; const DEFAULT_EMERGENCY_PICKER: bool = true; + const DEFAULT_MAX_LEVEL: u32 = 6; + const DEFAULT_MAX_L0_COMPACT_LEVEL_COUNT: u32 = 42; use crate::catalog::hummock::CompactionFilterFlag; pub fn max_bytes_for_level_base() -> u64 { DEFAULT_MAX_BYTES_FOR_LEVEL_BASE } + pub fn max_bytes_for_level_multiplier() -> u64 { DEFAULT_LEVEL_MULTIPLIER } + pub fn max_compaction_bytes() -> u64 { DEFAULT_MAX_COMPACTION_BYTES } + pub fn sub_level_max_compaction_bytes() -> u64 { DEFAULT_MIN_COMPACTION_BYTES } + pub fn level0_tier_compact_file_number() -> u64 { DEFAULT_TIER_COMPACT_TRIGGER_NUMBER } + pub fn target_file_size_base() -> u64 { DEFAULT_TARGET_FILE_SIZE_BASE } + pub fn compaction_filter_mask() -> u32 { (CompactionFilterFlag::STATE_CLEAN | CompactionFilterFlag::TTL).into() } + pub fn max_sub_compaction() -> u32 { DEFAULT_MAX_SUB_COMPACTION } + pub fn level0_stop_write_threshold_sub_level_number() -> u64 { DEFAULT_LEVEL0_STOP_WRITE_THRESHOLD_SUB_LEVEL_NUMBER } + pub fn level0_sub_level_compact_level_count() -> u32 { DEFAULT_MIN_SUB_LEVEL_COMPACT_LEVEL_COUNT } + pub fn level0_overlapping_sub_level_compact_level_count() -> u32 { DEFAULT_MIN_OVERLAPPING_SUB_LEVEL_COMPACT_LEVEL_COUNT } + pub fn max_space_reclaim_bytes() -> u64 { DEFAULT_MAX_SPACE_RECLAIM_BYTES } + pub fn level0_max_compact_file_number() -> u64 { DEFAULT_MAX_COMPACTION_FILE_COUNT } + pub fn tombstone_reclaim_ratio() -> u32 { DEFAULT_TOMBSTONE_RATIO_PERCENT } @@ -1667,79 +1936,143 @@ pub mod default { pub fn enable_emergency_picker() -> bool { DEFAULT_EMERGENCY_PICKER } + + pub fn max_level() -> u32 { + DEFAULT_MAX_LEVEL + } + + pub fn max_l0_compact_level_count() -> u32 { + DEFAULT_MAX_L0_COMPACT_LEVEL_COUNT + } } pub mod object_store_config { - pub fn object_store_streaming_read_timeout_ms() -> u64 { - 8 * 60 * 1000 + const DEFAULT_REQ_BACKOFF_INTERVAL_MS: u64 = 1000; // 1s + const DEFAULT_REQ_BACKOFF_MAX_DELAY_MS: u64 = 10 * 1000; // 10s + const DEFAULT_REQ_MAX_RETRY_ATTEMPTS: usize = 3; + + pub fn set_atomic_write_dir() -> bool { + false } - pub fn object_store_streaming_upload_timeout_ms() -> u64 { - 8 * 60 * 1000 + pub fn object_store_req_backoff_interval_ms() -> u64 { + DEFAULT_REQ_BACKOFF_INTERVAL_MS } - pub fn object_store_upload_timeout_ms() -> u64 { - 8 * 60 * 1000 + pub fn object_store_req_backoff_max_delay_ms() -> u64 { + DEFAULT_REQ_BACKOFF_MAX_DELAY_MS // 10s } - pub fn object_store_read_timeout_ms() -> u64 { - 8 * 60 * 1000 + pub fn object_store_req_backoff_factor() -> u64 { + 2 } - pub fn object_store_set_atomic_write_dir() -> bool { - false + pub fn object_store_upload_attempt_timeout_ms() -> u64 { + 8 * 1000 // 8s + } + + pub fn object_store_upload_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + // init + upload_part + finish + pub fn object_store_streaming_upload_attempt_timeout_ms() -> u64 { + 5 * 1000 // 5s + } + + pub fn object_store_streaming_upload_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + // tips: depend on block_size + pub fn object_store_read_attempt_timeout_ms() -> u64 { + 8 * 1000 // 8s + } + + pub fn object_store_read_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + pub fn object_store_streaming_read_attempt_timeout_ms() -> u64 { + 3 * 1000 // 3s + } + + pub fn object_store_streaming_read_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + pub fn object_store_metadata_attempt_timeout_ms() -> u64 { + 60 * 1000 // 1min + } + + pub fn object_store_metadata_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + pub fn object_store_delete_attempt_timeout_ms() -> u64 { + 5 * 1000 + } + + pub fn object_store_delete_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + // tips: depend on batch size + pub fn object_store_delete_objects_attempt_timeout_ms() -> u64 { + 5 * 1000 + } + + pub fn object_store_delete_objects_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + pub fn object_store_list_attempt_timeout_ms() -> u64 { + 10 * 60 * 1000 + } + + pub fn object_store_list_retry_attempts() -> usize { + DEFAULT_REQ_MAX_RETRY_ATTEMPTS + } + + pub fn opendal_upload_concurrency() -> usize { + 8 } pub mod s3 { - /// Retry config for compute node http timeout error. - const DEFAULT_RETRY_INTERVAL_MS: u64 = 20; - const DEFAULT_RETRY_MAX_DELAY_MS: u64 = 10 * 1000; - const DEFAULT_RETRY_MAX_ATTEMPTS: usize = 8; const DEFAULT_IDENTITY_RESOLUTION_TIMEOUT_S: u64 = 5; const DEFAULT_KEEPALIVE_MS: u64 = 600 * 1000; // 10min - pub fn object_store_keepalive_ms() -> Option { + pub fn keepalive_ms() -> Option { Some(DEFAULT_KEEPALIVE_MS) // 10min } - pub fn object_store_recv_buffer_size() -> Option { + pub fn recv_buffer_size() -> Option { Some(1 << 21) // 2m } - pub fn object_store_send_buffer_size() -> Option { + pub fn send_buffer_size() -> Option { None } - pub fn object_store_nodelay() -> Option { + pub fn nodelay() -> Option { Some(true) } - pub fn object_store_req_retry_interval_ms() -> u64 { - DEFAULT_RETRY_INTERVAL_MS - } - - pub fn object_store_req_retry_max_delay_ms() -> u64 { - DEFAULT_RETRY_MAX_DELAY_MS // 10s - } - - pub fn object_store_req_retry_max_attempts() -> usize { - DEFAULT_RETRY_MAX_ATTEMPTS - } - pub fn identity_resolution_timeout_s() -> u64 { DEFAULT_IDENTITY_RESOLUTION_TIMEOUT_S } pub mod developer { use crate::util::env_var::env_var_is_true_or; + const RW_USE_OPENDAL_FOR_S3: &str = "RW_USE_OPENDAL_FOR_S3"; - pub fn object_store_retry_unknown_service_error() -> bool { + pub fn retry_unknown_service_error() -> bool { false } - pub fn object_store_retryable_service_error_codes() -> Vec { + pub fn retryable_service_error_codes() -> Vec { vec!["SlowDown".into(), "TooManyRequests".into()] } @@ -1770,6 +2103,16 @@ impl EvictionConfig { } } +impl From for foyer::EvictionConfig { + fn from(value: EvictionConfig) -> Self { + match value { + EvictionConfig::Lru(lru) => foyer::EvictionConfig::Lru(lru), + EvictionConfig::Lfu(lfu) => foyer::EvictionConfig::Lfu(lfu), + EvictionConfig::S3Fifo(s3fifo) => foyer::EvictionConfig::S3Fifo(s3fifo), + } + } +} + pub struct StorageMemoryConfig { pub block_cache_capacity_mb: usize, pub block_cache_shard_num: usize, @@ -1856,11 +2199,19 @@ pub fn extract_storage_memory_config(s: &RwConfig) -> StorageMemoryConfig { }), CacheEvictionConfig::S3Fifo { small_queue_capacity_ratio_in_percent, + ghost_queue_capacity_ratio_in_percent, + small_to_main_freq_threshold, } => EvictionConfig::S3Fifo(S3FifoConfig { small_queue_capacity_ratio: small_queue_capacity_ratio_in_percent .unwrap_or(default::storage::small_queue_capacity_ratio_in_percent()) as f64 / 100.0, + ghost_queue_capacity_ratio: ghost_queue_capacity_ratio_in_percent + .unwrap_or(default::storage::ghost_queue_capacity_ratio_in_percent()) + as f64 + / 100.0, + small_to_main_freq_threshold: small_to_main_freq_threshold + .unwrap_or(default::storage::small_to_main_freq_threshold()), }), } }; @@ -1930,14 +2281,21 @@ pub struct CompactionConfig { pub tombstone_reclaim_ratio: u32, #[serde(default = "default::compaction_config::enable_emergency_picker")] pub enable_emergency_picker: bool, + #[serde(default = "default::compaction_config::max_level")] + pub max_level: u32, } #[cfg(test)] mod tests { - use std::collections::BTreeMap; - use super::*; + fn default_config_for_docs() -> RwConfig { + let mut config = RwConfig::default(); + // Set `license_key` to empty to avoid showing the test-only license key in the docs. + config.system.license_key = Some("".to_owned()); + config + } + /// This test ensures that `config/example.toml` is up-to-date with the default values specified /// in this file. Developer should run `./risedev generate-example-config` to update it if this /// test fails. @@ -1947,7 +2305,7 @@ mod tests { # Check detailed comments in src/common/src/config.rs"; let actual = expect_test::expect_file!["../../config/example.toml"]; - let default = toml::to_string(&RwConfig::default()).expect("failed to serialize"); + let default = toml::to_string(&default_config_for_docs()).expect("failed to serialize"); let expected = format!("{HEADER}\n\n{default}"); actual.assert_eq(&expected); @@ -1988,7 +2346,7 @@ mod tests { .collect(); let toml_doc: BTreeMap = - toml::from_str(&toml::to_string(&RwConfig::default()).unwrap()).unwrap(); + toml::from_str(&toml::to_string(&default_config_for_docs()).unwrap()).unwrap(); toml_doc.into_iter().for_each(|(name, value)| { set_default_values("".to_string(), name, value, &mut configs); }); @@ -2036,4 +2394,99 @@ mod tests { } } } + + #[test] + fn test_object_store_configs_backward_compatibility() { + // Define configs with the old name and make sure it still works + { + let config: RwConfig = toml::from_str( + r#" + [storage.object_store] + object_store_set_atomic_write_dir = true + + [storage.object_store.s3] + object_store_keepalive_ms = 1 + object_store_send_buffer_size = 1 + object_store_recv_buffer_size = 1 + object_store_nodelay = false + + [storage.object_store.s3.developer] + object_store_retry_unknown_service_error = true + object_store_retryable_service_error_codes = ['dummy'] + + + "#, + ) + .unwrap(); + + assert!(config.storage.object_store.set_atomic_write_dir); + assert_eq!(config.storage.object_store.s3.keepalive_ms, Some(1)); + assert_eq!(config.storage.object_store.s3.send_buffer_size, Some(1)); + assert_eq!(config.storage.object_store.s3.recv_buffer_size, Some(1)); + assert_eq!(config.storage.object_store.s3.nodelay, Some(false)); + assert!( + config + .storage + .object_store + .s3 + .developer + .retry_unknown_service_error + ); + assert_eq!( + config + .storage + .object_store + .s3 + .developer + .retryable_service_error_codes, + vec!["dummy".to_string()] + ); + } + + // Define configs with the new name and make sure it works + { + let config: RwConfig = toml::from_str( + r#" + [storage.object_store] + set_atomic_write_dir = true + + [storage.object_store.s3] + keepalive_ms = 1 + send_buffer_size = 1 + recv_buffer_size = 1 + nodelay = false + + [storage.object_store.s3.developer] + retry_unknown_service_error = true + retryable_service_error_codes = ['dummy'] + + + "#, + ) + .unwrap(); + + assert!(config.storage.object_store.set_atomic_write_dir); + assert_eq!(config.storage.object_store.s3.keepalive_ms, Some(1)); + assert_eq!(config.storage.object_store.s3.send_buffer_size, Some(1)); + assert_eq!(config.storage.object_store.s3.recv_buffer_size, Some(1)); + assert_eq!(config.storage.object_store.s3.nodelay, Some(false)); + assert!( + config + .storage + .object_store + .s3 + .developer + .retry_unknown_service_error + ); + assert_eq!( + config + .storage + .object_store + .s3 + .developer + .retryable_service_error_codes, + vec!["dummy".to_string()] + ); + } + } } diff --git a/src/common/src/field_generator/varchar.rs b/src/common/src/field_generator/varchar.rs index c4af1499620ca..9f68bcb287c46 100644 --- a/src/common/src/field_generator/varchar.rs +++ b/src/common/src/field_generator/varchar.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::string::ToString; - use rand::distributions::Alphanumeric; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; diff --git a/src/common/src/hash/consistent_hash/bitmap.rs b/src/common/src/hash/consistent_hash/bitmap.rs index f6e55f3ebec50..773231ba36a89 100644 --- a/src/common/src/hash/consistent_hash/bitmap.rs +++ b/src/common/src/hash/consistent_hash/bitmap.rs @@ -14,7 +14,7 @@ use std::ops::RangeInclusive; -use crate::buffer::Bitmap; +use crate::bitmap::Bitmap; use crate::hash::VirtualNode; /// An extension trait for `Bitmap` to support virtual node operations. diff --git a/src/common/src/hash/consistent_hash/mapping.rs b/src/common/src/hash/consistent_hash/mapping.rs index c542ab2050cf1..51e3aa02d262c 100644 --- a/src/common/src/hash/consistent_hash/mapping.rs +++ b/src/common/src/hash/consistent_hash/mapping.rs @@ -12,25 +12,62 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; -use std::fmt::Debug; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::{Debug, Display, Formatter}; use std::hash::Hash; -use std::ops::Index; +use std::ops::{Index, Sub}; use educe::Educe; use itertools::Itertools; -use risingwave_pb::common::{ParallelUnit, ParallelUnitMapping as ParallelUnitMappingProto}; +use risingwave_pb::common::{ + ParallelUnit, ParallelUnitMapping as ParallelUnitMappingProto, PbWorkerSlotMapping, +}; use risingwave_pb::stream_plan::ActorMapping as ActorMappingProto; use super::bitmap::VnodeBitmapExt; use super::vnode::{ParallelUnitId, VirtualNode}; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::util::compress::compress_data; use crate::util::iter_util::ZipEqDebug; // TODO: find a better place for this. pub type ActorId = u32; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub struct WorkerSlotId(u64); + +impl WorkerSlotId { + pub fn worker_id(&self) -> u32 { + (self.0 >> 32) as u32 + } + + pub fn slot_idx(&self) -> u32 { + self.0 as u32 + } + + pub fn new(worker_id: u32, slot_idx: usize) -> Self { + Self((worker_id as u64) << 32 | slot_idx as u64) + } +} + +impl From for u64 { + fn from(id: WorkerSlotId) -> Self { + id.0 + } +} + +impl From for WorkerSlotId { + fn from(id: u64) -> Self { + Self(id) + } +} + +impl Display for WorkerSlotId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("[{}:{}]", self.worker_id(), self.slot_idx())) + } +} + /// Trait for items that can be used as keys in [`VnodeMapping`]. pub trait VnodeMappingItem { /// The type of the item. @@ -254,6 +291,12 @@ pub mod marker { impl VnodeMappingItem for ParallelUnit { type Item = ParallelUnitId; } + + /// A marker type for items of [`WorkerSlotId`]. + pub struct WorkerSlot; + impl VnodeMappingItem for WorkerSlot { + type Item = WorkerSlotId; + } } /// A mapping from [`VirtualNode`] to [`ActorId`]. @@ -266,6 +309,11 @@ pub type ParallelUnitMapping = VnodeMapping; /// An expanded mapping from [`VirtualNode`] to [`ParallelUnitId`]. pub type ExpandedParallelUnitMapping = ExpandedMapping; +/// A mapping from [`VirtualNode`] to [`WorkerSlotId`]. +pub type WorkerSlotMapping = VnodeMapping; +/// An expanded mapping from [`VirtualNode`] to [`WorkerSlotId`]. +pub type ExpandedWorkerSlotMapping = ExpandedMapping; + impl ActorMapping { /// Transform this actor mapping to a parallel unit mapping, essentially `transform`. pub fn to_parallel_unit(&self, to_map: &M) -> ParallelUnitMapping @@ -293,6 +341,36 @@ impl ActorMapping { } } +#[derive(thiserror::Error, Debug)] +pub enum ParallelUnitError { + #[error("parallel units {0:?} are not covered by the worker slot mapping")] + NotCovered(HashSet), +} + +impl WorkerSlotMapping { + /// Create a uniform worker mapping from the given worker ids + pub fn build_from_ids(worker_slot_ids: &[WorkerSlotId]) -> Self { + Self::new_uniform(worker_slot_ids.iter().cloned()) + } + + /// Create a worker mapping from the protobuf representation. + pub fn from_protobuf(proto: &PbWorkerSlotMapping) -> Self { + assert_eq!(proto.original_indices.len(), proto.data.len()); + Self { + original_indices: proto.original_indices.clone(), + data: proto.data.iter().map(|&id| WorkerSlotId(id)).collect(), + } + } + + /// Convert this worker mapping to the protobuf representation. + pub fn to_protobuf(&self) -> PbWorkerSlotMapping { + PbWorkerSlotMapping { + original_indices: self.original_indices.clone(), + data: self.data.iter().map(|id| id.0).collect(), + } + } +} + impl ParallelUnitMapping { /// Create a uniform parallel unit mapping from the given parallel units, essentially /// `new_uniform`. @@ -310,6 +388,41 @@ impl ParallelUnitMapping { self.transform(to_map) } + /// Transform this parallel unit mapping to a worker slot mapping, essentially `transform`. + pub fn to_worker_slot( + &self, + to_map: &HashMap, + ) -> Result { + let mut worker_to_parallel_units = HashMap::<_, BTreeSet<_>>::new(); + for (parallel_unit_id, worker_id) in to_map { + worker_to_parallel_units + .entry(*worker_id) + .or_default() + .insert(*parallel_unit_id); + } + + let mut parallel_unit_to_worker_slot = HashMap::with_capacity(to_map.len()); + + for (worker_id, parallel_unit_ids) in worker_to_parallel_units { + for (index, ¶llel_unit_id) in parallel_unit_ids.iter().enumerate() { + parallel_unit_to_worker_slot + .insert(parallel_unit_id, WorkerSlotId::new(worker_id, index)); + } + } + + let available_parallel_unit_ids: HashSet<_> = + parallel_unit_to_worker_slot.keys().copied().collect(); + + let parallel_unit_ids: HashSet<_> = self.data.iter().copied().collect(); + + let sub_set = parallel_unit_ids.sub(&available_parallel_unit_ids); + if sub_set.is_empty() { + Ok(self.transform(¶llel_unit_to_worker_slot)) + } else { + Err(ParallelUnitError::NotCovered(sub_set)) + } + } + /// Create a parallel unit mapping from the protobuf representation. pub fn from_protobuf(proto: &ParallelUnitMappingProto) -> Self { assert_eq!(proto.original_indices.len(), proto.data.len()); @@ -335,7 +448,6 @@ mod tests { use rand::Rng; use super::*; - use crate::util::iter_util::ZipEqDebug; struct Test; impl VnodeMappingItem for Test { diff --git a/src/common/src/hash/consistent_hash/vnode.rs b/src/common/src/hash/consistent_hash/vnode.rs index 9bc49a9372ac0..fc6dac0978adf 100644 --- a/src/common/src/hash/consistent_hash/vnode.rs +++ b/src/common/src/hash/consistent_hash/vnode.rs @@ -169,7 +169,6 @@ mod tests { use super::*; use crate::array::DataChunkTestExt; use crate::row::OwnedRow; - use crate::types::ScalarImpl; use crate::util::row_id::RowIdGenerator; #[test] diff --git a/src/common/src/hash/key.rs b/src/common/src/hash/key.rs index c136710edb23f..e9f7e83ac9146 100644 --- a/src/common/src/hash/key.rs +++ b/src/common/src/hash/key.rs @@ -21,7 +21,6 @@ //! are encoded from both `t.b` and `t.c`. If `t.b="abc"` and `t.c=1`, the hashkey may be //! encoded in certain format of `("abc", 1)`. -use std::convert::TryInto; use std::default::Default; use std::fmt::Debug; use std::hash::{BuildHasher, Hasher}; @@ -237,7 +236,7 @@ impl From for HashCode { } impl HashCode { - pub fn value(self) -> u64 { + pub fn value(&self) -> u64 { self.value } } @@ -568,8 +567,8 @@ impl HashKeyDe for Date { impl HashKeySer<'_> for Timestamp { fn serialize_into(self, mut buf: impl BufMut) { - buf.put_i64_ne(self.0.timestamp()); - buf.put_u32_ne(self.0.timestamp_subsec_nanos()); + buf.put_i64_ne(self.0.and_utc().timestamp()); + buf.put_u32_ne(self.0.and_utc().timestamp_subsec_nanos()); } fn exact_size() -> Option { @@ -643,11 +642,9 @@ mod tests { DateArray, DecimalArray, F32Array, F64Array, I16Array, I32Array, I32ArrayBuilder, I64Array, TimeArray, TimestampArray, Utf8Array, }; - use crate::hash::{ - HashKey, Key128, Key16, Key256, Key32, Key64, KeySerialized, PrecomputedBuildHasher, - }; + use crate::hash::{HashKey, Key128, Key16, Key256, Key32, Key64, KeySerialized}; use crate::test_utils::rand_array::seed_rand_array_ref; - use crate::types::{DataType, Datum}; + use crate::types::Datum; #[derive(Hash, PartialEq, Eq)] struct Row(Vec); diff --git a/src/common/src/hash/table_distribution.rs b/src/common/src/hash/table_distribution.rs index c5bb2fd3d8dd2..9be9cd2abafb2 100644 --- a/src/common/src/hash/table_distribution.rs +++ b/src/common/src/hash/table_distribution.rs @@ -21,7 +21,7 @@ use risingwave_pb::plan_common::StorageTableDesc; use tracing::warn; use crate::array::{Array, DataChunk, PrimitiveArray}; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; use crate::hash::VirtualNode; use crate::row::Row; use crate::util::iter_util::ZipEqFast; @@ -37,7 +37,7 @@ enum ComputeVnode { dist_key_in_pk_indices: Vec, }, VnodeColumnIndex { - /// Indices of vnode columns. + /// Index of vnode column. vnode_col_idx_in_pk: usize, }, } @@ -87,7 +87,7 @@ impl TableDistribution { let vnodes = vnodes.unwrap_or_else(Self::singleton_vnode_bitmap); if let ComputeVnode::Singleton = &compute_vnode { - if &vnodes != Self::singleton_vnode_bitmap_ref() { + if &vnodes != Self::singleton_vnode_bitmap_ref() && &vnodes != Self::all_vnodes_ref() { warn!( ?vnodes, "singleton distribution get non-singleton vnode bitmap" diff --git a/src/common/src/lib.rs b/src/common/src/lib.rs index ea3cd4a4f7102..0681554dbb997 100644 --- a/src/common/src/lib.rs +++ b/src/common/src/lib.rs @@ -34,16 +34,14 @@ #![feature(inline_const_pat)] #![allow(incomplete_features)] #![feature(iterator_try_collect)] -#![feature(round_ties_even)] #![feature(iter_order_by)] #![feature(exclusive_range_pattern)] #![feature(binary_heap_into_iter_sorted)] #![feature(impl_trait_in_assoc_type)] #![feature(map_entry_replace)] #![feature(negative_impls)] -#![feature(bound_map)] -#![feature(array_methods)] #![feature(register_tool)] +#![feature(btree_cursors)] #![register_tool(rw)] #[cfg_attr(not(test), allow(unused_extern_crates))] @@ -67,7 +65,7 @@ pub mod array; #[macro_use] pub mod util; pub mod acl; -pub mod buffer; +pub mod bitmap; pub mod cache; pub mod cast; pub mod catalog; @@ -77,15 +75,17 @@ pub mod field_generator; pub mod hash; pub mod log; pub mod memory; -pub use risingwave_common_metrics as metrics; pub use risingwave_common_metrics::{ monitor, register_guarded_gauge_vec_with_registry, register_guarded_histogram_vec_with_registry, register_guarded_int_counter_vec_with_registry, register_guarded_int_gauge_vec_with_registry, }; +pub use {risingwave_common_metrics as metrics, risingwave_license as license}; +pub mod lru; pub mod opts; pub mod range; pub mod row; +pub mod sequence; pub mod session_config; pub mod system_param; pub mod telemetry; diff --git a/src/common/src/lru.rs b/src/common/src/lru.rs new file mode 100644 index 0000000000000..191c0cdf5579e --- /dev/null +++ b/src/common/src/lru.rs @@ -0,0 +1,369 @@ +// Copyright 2024 RisingWave Labs +// +// 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::alloc::{Allocator, Global}; +use std::borrow::Borrow; +use std::cell::RefCell; +use std::hash::{BuildHasher, Hash}; +use std::mem::MaybeUninit; +use std::ptr::NonNull; +use std::sync::atomic::Ordering; + +pub use ahash::RandomState; +use hashbrown::hash_table::Entry; +use hashbrown::HashTable; + +use crate::sequence::{AtomicSequence, Sequence, Sequencer}; + +thread_local! { + pub static SEQUENCER: RefCell = const { RefCell::new(Sequencer::new(Sequencer::DEFAULT_STEP, Sequencer::DEFAULT_LAG)) }; +} + +static SEQUENCER_DEFAULT_STEP: AtomicSequence = AtomicSequence::new(Sequencer::DEFAULT_STEP); +static SEQUENCER_DEFAULT_LAG: AtomicSequence = AtomicSequence::new(Sequencer::DEFAULT_LAG); + +pub fn init_global_sequencer_args(step: Sequence, lag: Sequence) { + SEQUENCER_DEFAULT_STEP.store(step, Ordering::Relaxed); + SEQUENCER_DEFAULT_LAG.store(lag, Ordering::Relaxed); +} + +struct LruEntry +where + K: Hash + Eq, +{ + prev: Option>>, + next: Option>>, + key: MaybeUninit, + value: MaybeUninit, + hash: u64, + sequence: Sequence, +} + +impl LruEntry +where + K: Hash + Eq, +{ + fn key(&self) -> &K { + unsafe { self.key.assume_init_ref() } + } + + fn value(&self) -> &V { + unsafe { self.value.assume_init_ref() } + } + + fn value_mut(&mut self) -> &mut V { + unsafe { self.value.assume_init_mut() } + } +} + +unsafe impl Send for LruEntry where K: Hash + Eq {} +unsafe impl Sync for LruEntry where K: Hash + Eq {} + +pub struct LruCache +where + K: Hash + Eq, + S: BuildHasher + Send + Sync + 'static, + A: Clone + Allocator, +{ + map: HashTable>, A>, + /// dummy node of the lru linked list + dummy: Box, A>, + + alloc: A, + hash_builder: S, +} + +unsafe impl Send for LruCache +where + K: Hash + Eq, + S: BuildHasher + Send + Sync + 'static, + A: Clone + Allocator, +{ +} +unsafe impl Sync for LruCache +where + K: Hash + Eq, + S: BuildHasher + Send + Sync + 'static, + A: Clone + Allocator, +{ +} + +impl LruCache +where + K: Hash + Eq, +{ + pub fn unbounded() -> Self { + Self::unbounded_with_hasher_in(RandomState::default(), Global) + } +} + +impl LruCache +where + K: Hash + Eq, + S: BuildHasher + Send + Sync + 'static, + A: Clone + Allocator, +{ + pub fn unbounded_with_hasher_in(hash_builder: S, alloc: A) -> Self { + let map = HashTable::new_in(alloc.clone()); + let mut dummy = Box::new_in( + LruEntry { + prev: None, + next: None, + key: MaybeUninit::uninit(), + value: MaybeUninit::uninit(), + hash: 0, + sequence: Sequence::default(), + }, + alloc.clone(), + ); + let ptr = unsafe { NonNull::new_unchecked(dummy.as_mut() as *mut _) }; + dummy.next = Some(ptr); + dummy.prev = Some(ptr); + Self { + map, + dummy, + alloc, + hash_builder, + } + } + + pub fn put(&mut self, key: K, mut value: V) -> Option { + unsafe { + let hash = self.hash_builder.hash_one(&key); + + match self + .map + .entry(hash, |p| p.as_ref().key() == &key, |p| p.as_ref().hash) + { + Entry::Occupied(o) => { + let mut ptr = *o.get(); + let entry = ptr.as_mut(); + std::mem::swap(&mut value, entry.value_mut()); + self.detach(ptr); + self.attach(ptr); + Some(value) + } + Entry::Vacant(v) => { + let entry = Box::new_in( + LruEntry { + prev: None, + next: None, + key: MaybeUninit::new(key), + value: MaybeUninit::new(value), + hash, + // sequence will be updated by `attach` + sequence: 0, + }, + self.alloc.clone(), + ); + let ptr = NonNull::new_unchecked(Box::into_raw(entry)); + v.insert(ptr); + self.attach(ptr); + None + } + } + } + } + + pub fn get<'a, Q>(&'a mut self, key: &Q) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + unsafe { + let key = key.borrow(); + let hash = self.hash_builder.hash_one(key); + if let Some(ptr) = self.map.find(hash, |p| p.as_ref().key().borrow() == key) { + let ptr = *ptr; + self.detach(ptr); + self.attach(ptr); + Some(ptr.as_ref().value()) + } else { + None + } + } + } + + pub fn get_mut<'a, Q>(&'a mut self, key: &Q) -> Option<&'a mut V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + unsafe { + let key = key.borrow(); + let hash = self.hash_builder.hash_one(key); + if let Some(ptr) = self + .map + .find_mut(hash, |p| p.as_ref().key().borrow() == key) + { + let mut ptr = *ptr; + self.detach(ptr); + self.attach(ptr); + Some(ptr.as_mut().value_mut()) + } else { + None + } + } + } + + pub fn peek<'a, Q>(&'a self, key: &Q) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + unsafe { + let key = key.borrow(); + let hash = self.hash_builder.hash_one(key); + self.map + .find(hash, |p| p.as_ref().key().borrow() == key) + .map(|ptr| ptr.as_ref().value()) + } + } + + pub fn peek_mut<'a, Q>(&'a mut self, key: &Q) -> Option<&'a mut V> + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + unsafe { + let key = key.borrow(); + let hash = self.hash_builder.hash_one(key); + self.map + .find(hash, |p| p.as_ref().key().borrow() == key) + .map(|ptr| ptr.clone().as_mut().value_mut()) + } + } + + pub fn contains(&self, key: &Q) -> bool + where + K: Borrow, + Q: Hash + Eq + ?Sized, + { + unsafe { + let key = key.borrow(); + let hash = self.hash_builder.hash_one(key); + self.map + .find(hash, |p| p.as_ref().key().borrow() == key) + .is_some() + } + } + + pub fn len(&self) -> usize { + self.map.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Pop first entry if its sequence is less than the given sequence. + pub fn pop_with_sequence(&mut self, sequence: Sequence) -> Option<(K, V, Sequence)> { + unsafe { + if self.is_empty() { + return None; + } + + let ptr = self.dummy.next.unwrap_unchecked(); + if ptr.as_ref().sequence >= sequence { + return None; + } + + self.detach(ptr); + + let entry = Box::from_raw_in(ptr.as_ptr(), self.alloc.clone()); + + let key = entry.key.assume_init(); + let value = entry.value.assume_init(); + let sequence = entry.sequence; + + let hash = self.hash_builder.hash_one(&key); + + match self + .map + .entry(hash, |p| p.as_ref().key() == &key, |p| p.as_ref().hash) + { + Entry::Occupied(o) => { + o.remove(); + } + Entry::Vacant(_) => {} + } + + Some((key, value, sequence)) + } + } + + pub fn clear(&mut self) { + unsafe { + let mut map = HashTable::new_in(self.alloc.clone()); + std::mem::swap(&mut map, &mut self.map); + + for ptr in map.drain() { + self.detach(ptr); + let mut entry = Box::from_raw_in(ptr.as_ptr(), self.alloc.clone()); + entry.key.assume_init_drop(); + entry.value.assume_init_drop(); + } + + debug_assert!(self.is_empty()); + debug_assert_eq!( + self.dummy.as_mut() as *mut _, + self.dummy.next.unwrap_unchecked().as_ptr() + ) + } + } + + fn detach(&mut self, mut ptr: NonNull>) { + unsafe { + let entry = ptr.as_mut(); + + debug_assert!(entry.prev.is_some() && entry.next.is_some()); + + entry.prev.unwrap_unchecked().as_mut().next = entry.next; + entry.next.unwrap_unchecked().as_mut().prev = entry.prev; + + entry.next = None; + entry.prev = None; + } + } + + fn attach(&mut self, mut ptr: NonNull>) { + unsafe { + let entry = ptr.as_mut(); + + debug_assert!(entry.prev.is_none() && entry.next.is_none()); + + entry.next = Some(NonNull::new_unchecked(self.dummy.as_mut() as *mut _)); + entry.prev = self.dummy.prev; + + self.dummy.prev.unwrap_unchecked().as_mut().next = Some(ptr); + self.dummy.prev = Some(ptr); + + entry.sequence = SEQUENCER.with(|s| s.borrow_mut().alloc()); + } + } +} + +impl Drop for LruCache +where + K: Hash + Eq, + S: BuildHasher + Send + Sync + 'static, + A: Clone + Allocator, +{ + fn drop(&mut self) { + self.clear() + } +} + +#[cfg(test)] +mod tests {} diff --git a/src/common/src/memory/alloc.rs b/src/common/src/memory/alloc.rs index 81413a623239c..acff8f02ce631 100644 --- a/src/common/src/memory/alloc.rs +++ b/src/common/src/memory/alloc.rs @@ -43,13 +43,14 @@ impl MonitoredGlobalAlloc { unsafe impl Allocator for MonitoredAlloc { fn allocate(&self, layout: Layout) -> Result, AllocError> { let ret = self.alloc.allocate(layout)?; + // We don't throw an AllocError if the memory context is out of memory, otherwise the whole process will crash. self.ctx.add(layout.size() as i64); Ok(ret) } unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { self.alloc.deallocate(ptr, layout); - self.ctx.add(-(layout.size() as i64)) + self.ctx.add(-(layout.size() as i64)); } } diff --git a/src/common/src/memory/mem_context.rs b/src/common/src/memory/mem_context.rs index 5c538d9038fb4..c484bf90aa146 100644 --- a/src/common/src/memory/mem_context.rs +++ b/src/common/src/memory/mem_context.rs @@ -59,6 +59,7 @@ impl MemCounter for LabelGuardedIntGauge { struct MemoryContextInner { counter: Box, parent: Option, + mem_limit: u64, } #[derive(Clone)] @@ -70,9 +71,22 @@ pub struct MemoryContext { impl MemoryContext { pub fn new(parent: Option, counter: impl MemCounter) -> Self { + let mem_limit = parent.as_ref().map_or_else(|| u64::MAX, |p| p.mem_limit()); + Self::new_with_mem_limit(parent, counter, mem_limit) + } + + pub fn new_with_mem_limit( + parent: Option, + counter: impl MemCounter, + mem_limit: u64, + ) -> Self { let c = Box::new(counter); Self { - inner: Some(Arc::new(MemoryContextInner { counter: c, parent })), + inner: Some(Arc::new(MemoryContextInner { + counter: c, + parent, + mem_limit, + })), } } @@ -81,19 +95,32 @@ impl MemoryContext { Self { inner: None } } - pub fn root(counter: impl MemCounter) -> Self { - Self::new(None, counter) + pub fn root(counter: impl MemCounter, mem_limit: u64) -> Self { + Self::new_with_mem_limit(None, counter, mem_limit) + } + + pub fn for_spill_test() -> Self { + Self::new_with_mem_limit(None, TrAdderAtomic::new(0), 0) } /// Add `bytes` memory usage. Pass negative value to decrease memory usage. - pub fn add(&self, bytes: i64) { + /// Returns `false` if the memory usage exceeds the limit. + pub fn add(&self, bytes: i64) -> bool { if let Some(inner) = &self.inner { - inner.counter.add(bytes); - + if (inner.counter.get_bytes_used() + bytes) as u64 > inner.mem_limit { + return false; + } if let Some(parent) = &inner.parent { - parent.add(bytes); + if parent.add(bytes) { + inner.counter.add(bytes); + } else { + return false; + } + } else { + inner.counter.add(bytes); } } + true } pub fn get_bytes_used(&self) -> i64 { @@ -104,6 +131,29 @@ impl MemoryContext { } } + pub fn mem_limit(&self) -> u64 { + if let Some(inner) = &self.inner { + inner.mem_limit + } else { + u64::MAX + } + } + + /// Check if the memory usage exceeds the limit. + /// Returns `false` if the memory usage exceeds the limit. + pub fn check_memory_usage(&self) -> bool { + if let Some(inner) = &self.inner { + if inner.counter.get_bytes_used() as u64 > inner.mem_limit { + return false; + } + if let Some(parent) = &inner.parent { + return parent.check_memory_usage(); + } + } + + true + } + /// Creates a new global allocator that reports memory usage to this context. pub fn global_allocator(&self) -> MonitoredGlobalAlloc { MonitoredGlobalAlloc::with_memory_context(self.clone()) @@ -113,7 +163,7 @@ impl MemoryContext { impl Drop for MemoryContextInner { fn drop(&mut self) { if let Some(p) = &self.parent { - p.add(-self.counter.get_bytes_used()) + p.add(-self.counter.get_bytes_used()); } } } diff --git a/src/common/src/memory/monitored_heap.rs b/src/common/src/memory/monitored_heap.rs index 58df24817a69b..5873194914edc 100644 --- a/src/common/src/memory/monitored_heap.rs +++ b/src/common/src/memory/monitored_heap.rs @@ -81,6 +81,10 @@ impl MemMonitoredHeap { self.mem_ctx.add(-((old_cap * size_of::()) as i64)); ret } + + pub fn mem_context(&self) -> &MemoryContext { + &self.mem_ctx + } } impl Extend for MemMonitoredHeap @@ -113,7 +117,7 @@ mod tests { #[test] fn test_heap() { let gauge = LabelGuardedIntGauge::<4>::test_int_gauge(); - let mem_ctx = MemoryContext::root(gauge.clone()); + let mem_ctx = MemoryContext::root(gauge.clone(), u64::MAX); let mut heap = MemMonitoredHeap::::new_with(mem_ctx); assert_eq!(0, gauge.get()); @@ -131,7 +135,7 @@ mod tests { #[test] fn test_heap_drop() { let gauge = LabelGuardedIntGauge::<4>::test_int_gauge(); - let mem_ctx = MemoryContext::root(gauge.clone()); + let mem_ctx = MemoryContext::root(gauge.clone(), u64::MAX); let vec = { let mut heap = MemMonitoredHeap::::new_with(mem_ctx); diff --git a/src/common/src/row/owned_row.rs b/src/common/src/row/owned_row.rs index 8ea3803538d84..15a01d192a64c 100644 --- a/src/common/src/row/owned_row.rs +++ b/src/common/src/row/owned_row.rs @@ -182,7 +182,7 @@ mod tests { use super::*; use crate::row::RowExt; - use crate::types::{DataType as Ty, Interval, ScalarImpl}; + use crate::types::DataType as Ty; use crate::util::hash_util::Crc32FastBuilder; #[test] diff --git a/src/common/src/sequence.rs b/src/common/src/sequence.rs new file mode 100644 index 0000000000000..91f469cae9c8f --- /dev/null +++ b/src/common/src/sequence.rs @@ -0,0 +1,86 @@ +// Copyright 2024 RisingWave Labs +// +// 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::atomic::{AtomicU64, Ordering}; + +pub type Sequence = u64; +pub type AtomicSequence = AtomicU64; + +pub static SEQUENCE_GLOBAL: AtomicSequence = AtomicSequence::new(0); + +/// An globally unique and approximate ascending sequence generator with local optimization. +/// +/// [`Sequencer`] can be used to generate globally unique sequence (id) in an approximate order. [`Sequencer`] allow +/// the generated sequence to be disordered in a certain window. The larger the allowed disordered sequence window is, +/// the better multithreading performance of the generator will be. +/// +/// The window is controlled with two arguments, `step` and `lag`. `step` controls the size of the batch of the +/// sequences to allocate by the local sequence generator. `lag` controls the maximum lag between the local generator +/// and the global generator to avoid skew. +pub struct Sequencer { + local: Sequence, + target: Sequence, + + step: Sequence, + lag: Sequence, +} + +impl Sequencer { + pub const DEFAULT_LAG: Sequence = Self::DEFAULT_STEP * 32; + pub const DEFAULT_STEP: Sequence = 64; + + /// Create a new local sequence generator. + pub const fn new(step: Sequence, lag: Sequence) -> Self { + Self { + local: 0, + target: 0, + step, + lag, + } + } + + /// Get the global sequence to allocate. + pub fn global(&self) -> Sequence { + SEQUENCE_GLOBAL.load(Ordering::Relaxed) + } + + /// Get the local sequence to allocate. + pub fn local(&self) -> Sequence { + self.local + } + + /// Allocate a new sequence. The allocated sequences from the same [`Sequencer`] are strictly ascending, the + /// allocated sequences from different [`Sequencer`]s are approximate ascending. + pub fn alloc(&mut self) -> Sequence { + self.try_alloc(); + let res = self.local; + self.local += 1; + res + } + + #[inline(always)] + fn try_alloc(&mut self) { + if self.local == self.target + || self.local + self.lag < SEQUENCE_GLOBAL.load(Ordering::Relaxed) + { + self.alloc_inner() + } + } + + #[inline(always)] + fn alloc_inner(&mut self) { + self.local = SEQUENCE_GLOBAL.fetch_add(self.step, Ordering::Relaxed); + self.target = self.local + self.step; + } +} diff --git a/src/common/src/session_config/mod.rs b/src/common/src/session_config/mod.rs index 4ee7617ee751d..8fbfd2cd6e969 100644 --- a/src/common/src/session_config/mod.rs +++ b/src/common/src/session_config/mod.rs @@ -152,7 +152,11 @@ pub struct SessionConfig { #[parameter(default = true, rename = "rw_streaming_enable_bushy_join")] streaming_enable_bushy_join: bool, - /// Enable arrangement backfill for streaming queries. Defaults to false. + /// Enable arrangement backfill for streaming queries. Defaults to true. + /// When set to true, the parallelism of the upstream fragment will be + /// decoupled from the parallelism of the downstream scan fragment. + /// Or more generally, the parallelism of the upstream table / index / mv + /// will be decoupled from the parallelism of the downstream table / index / mv / sink. #[parameter(default = true)] streaming_use_arrangement_backfill: bool, @@ -236,6 +240,10 @@ pub struct SessionConfig { #[parameter(default = 0)] lock_timeout: i32, + /// For limiting the startup time of a shareable CDC streaming source when the source is being created. Unit: seconds. + #[parameter(default = 30)] + cdc_source_wait_streaming_start_timeout: i32, + /// see . /// Unused in RisingWave, support for compatibility. #[parameter(default = true)] diff --git a/src/common/src/session_config/search_path.rs b/src/common/src/session_config/search_path.rs index 4573294ab24c4..7a99f07833b96 100644 --- a/src/common/src/session_config/search_path.rs +++ b/src/common/src/session_config/search_path.rs @@ -23,12 +23,12 @@ pub const USER_NAME_WILD_CARD: &str = "\"$user\""; /// see /// /// 1. when we `select` or `drop` object and don't give a specified schema, it will search the -/// object from the valid items in schema `rw_catalog`, `pg_catalog` and `search_path`. If schema -/// `rw_catalog` and `pg_catalog` are not in `search_path`, we will search them firstly. If they're -/// in `search_path`, we will follow the order in `search_path`. +/// object from the valid items in schema `rw_catalog`, `pg_catalog` and `search_path`. If schema +/// `rw_catalog` and `pg_catalog` are not in `search_path`, we will search them firstly. If they're +/// in `search_path`, we will follow the order in `search_path`. /// /// 2. when we `create` a `source` or `mv` and don't give a specified schema, it will use the first -/// valid schema in `search_path`. +/// valid schema in `search_path`. /// /// 3. when we `create` a `index` or `sink`, it will use the schema of the associated table. #[derive(Clone, Debug, PartialEq)] diff --git a/src/common/src/system_param/common.rs b/src/common/src/system_param/common.rs index d8ff741399533..7aec7d14f012b 100644 --- a/src/common/src/system_param/common.rs +++ b/src/common/src/system_param/common.rs @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use risingwave_license::LicenseManager; + use super::diff::SystemParamsDiff; use super::reader::SystemParamsReader; use crate::util::tracing::layer::toggle_otel_layer; /// Node-independent handler for system parameter changes. -/// -/// Currently, it is only used to enable or disable the distributed tracing layer. #[derive(Debug)] pub struct CommonHandler; @@ -32,8 +32,13 @@ impl CommonHandler { /// Handle the change of system parameters. pub fn handle_change(&self, diff: &SystemParamsDiff) { + // Toggle the distributed tracing layer. if let Some(enabled) = diff.enable_tracing { - toggle_otel_layer(enabled) + toggle_otel_layer(enabled); + } + // Refresh the license key. + if let Some(key) = diff.license_key.as_ref() { + LicenseManager::get().refresh(key); } } } diff --git a/src/common/src/system_param/local_manager.rs b/src/common/src/system_param/local_manager.rs index 5040d30f811d0..8a81959219704 100644 --- a/src/common/src/system_param/local_manager.rs +++ b/src/common/src/system_param/local_manager.rs @@ -48,6 +48,7 @@ impl LocalSystemParamsManager { let this = Self::new_inner(initial_params.clone()); // Spawn a task to run the common handler. + // TODO(bugen): this may be spawned multiple times under standalone deployment, though idempotent. tokio::spawn({ let mut rx = this.tx.subscribe(); async move { diff --git a/src/common/src/system_param/mod.rs b/src/common/src/system_param/mod.rs index 06d4cce2e4e6b..aa6f207ead1a9 100644 --- a/src/common/src/system_param/mod.rs +++ b/src/common/src/system_param/mod.rs @@ -30,6 +30,7 @@ use std::ops::RangeBounds; use std::str::FromStr; use paste::paste; +use risingwave_license::TEST_PAID_LICENSE_KEY; use risingwave_pb::meta::PbSystemParams; use self::diff::SystemParamsDiff; @@ -60,6 +61,15 @@ impl_param_value!(u64); impl_param_value!(f64); impl_param_value!(String => &'a str); +/// Set the default value of `license_key` to [`TEST_PAID_LICENSE_KEY`] in debug mode. +fn default_license_key() -> String { + if cfg!(debug_assertions) { + TEST_PAID_LICENSE_KEY.to_owned() + } else { + "".to_owned() + } +} + /// Define all system parameters here. /// /// To match all these information, write the match arm as follows: @@ -77,16 +87,18 @@ macro_rules! for_all_params { { barrier_interval_ms, u32, Some(1000_u32), true, "The interval of periodic barrier.", }, { checkpoint_frequency, u64, Some(1_u64), true, "There will be a checkpoint for every n barriers.", }, { sstable_size_mb, u32, Some(256_u32), false, "Target size of the Sstable.", }, - { parallel_compact_size_mb, u32, Some(512_u32), false, "", }, + { parallel_compact_size_mb, u32, Some(512_u32), false, "The size of parallel task for one compact/flush job.", }, { block_size_kb, u32, Some(64_u32), false, "Size of each block in bytes in SST.", }, { bloom_false_positive, f64, Some(0.001_f64), false, "False positive probability of bloom filter.", }, - { state_store, String, None, false, "", }, + { state_store, String, None, false, "URL for the state store", }, { data_directory, String, None, false, "Remote directory for storing data and metadata objects.", }, { backup_storage_url, String, None, true, "Remote storage url for storing snapshots.", }, { backup_storage_directory, String, None, true, "Remote directory for storing snapshots.", }, { max_concurrent_creating_streaming_jobs, u32, Some(1_u32), true, "Max number of concurrent creating streaming jobs.", }, { pause_on_next_bootstrap, bool, Some(false), true, "Whether to pause all data sources on next bootstrap.", }, { enable_tracing, bool, Some(false), true, "Whether to enable distributed tracing.", }, + { use_new_object_prefix_strategy, bool, None, false, "Whether to split object prefix.", }, + { license_key, String, Some(default_license_key()), true, "The license key to activate enterprise features.", }, } }; } @@ -147,6 +159,8 @@ macro_rules! def_default { pub mod default { use std::sync::LazyLock; + use super::*; + for_all_params!(def_default_opt); for_all_params!(def_default); } @@ -376,6 +390,7 @@ macro_rules! impl_system_params_for_test { ret.state_store = Some("hummock+memory".to_string()); ret.backup_storage_url = Some("memory".into()); ret.backup_storage_directory = Some("backup".into()); + ret.use_new_object_prefix_strategy = Some(false); ret } }; @@ -441,6 +456,8 @@ mod tests { (MAX_CONCURRENT_CREATING_STREAMING_JOBS_KEY, "1"), (PAUSE_ON_NEXT_BOOTSTRAP_KEY, "false"), (ENABLE_TRACING_KEY, "true"), + (USE_NEW_OBJECT_PREFIX_STRATEGY_KEY, "false"), + (LICENSE_KEY_KEY, "foo"), ("a_deprecated_param", "foo"), ]; diff --git a/src/common/src/system_param/reader.rs b/src/common/src/system_param/reader.rs index 3374e72120238..2ef13ff3f5509 100644 --- a/src/common/src/system_param/reader.rs +++ b/src/common/src/system_param/reader.rs @@ -137,6 +137,14 @@ where self.inner().data_directory.as_ref().unwrap() } + fn use_new_object_prefix_strategy(&self) -> bool { + *self + .inner() + .use_new_object_prefix_strategy + .as_ref() + .unwrap() + } + fn backup_storage_url(&self) -> &str { self.inner().backup_storage_url.as_ref().unwrap() } @@ -160,4 +168,8 @@ where .enable_tracing .unwrap_or_else(default::enable_tracing) } + + fn license_key(&self) -> &str { + self.inner().license_key.as_deref().unwrap_or_default() + } } diff --git a/src/common/src/telemetry/mod.rs b/src/common/src/telemetry/mod.rs index 54a88789a171f..9cf469af9cd8d 100644 --- a/src/common/src/telemetry/mod.rs +++ b/src/common/src/telemetry/mod.rs @@ -16,8 +16,10 @@ pub mod manager; pub mod pb_compatible; pub mod report; +use std::env; use std::time::SystemTime; +use risingwave_pb::telemetry::PbTelemetryClusterType; use serde::{Deserialize, Serialize}; use sysinfo::System; use thiserror_ext::AsReport; @@ -25,6 +27,27 @@ use thiserror_ext::AsReport; use crate::util::env_var::env_var_is_true_or; use crate::util::resource_util::cpu::total_cpu_available; use crate::util::resource_util::memory::{system_memory_available_bytes, total_memory_used_bytes}; +use crate::RW_VERSION; + +pub const TELEMETRY_CLUSTER_TYPE: &str = "RW_TELEMETRY_TYPE"; +pub const TELEMETRY_CLUSTER_TYPE_HOSTED: &str = "hosted"; // hosted on RisingWave Cloud +pub const TELEMETRY_CLUSTER_TYPE_KUBERNETES: &str = "kubernetes"; +pub const TELEMETRY_CLUSTER_TYPE_SINGLE_NODE: &str = "single-node"; +pub const TELEMETRY_CLUSTER_TYPE_DOCKER_COMPOSE: &str = "docker-compose"; + +pub fn telemetry_cluster_type_from_env_var() -> PbTelemetryClusterType { + let cluster_type = match env::var(TELEMETRY_CLUSTER_TYPE) { + Ok(cluster_type) => cluster_type, + Err(_) => return PbTelemetryClusterType::Unspecified, + }; + match cluster_type.as_str() { + TELEMETRY_CLUSTER_TYPE_HOSTED => PbTelemetryClusterType::CloudHosted, + TELEMETRY_CLUSTER_TYPE_DOCKER_COMPOSE => PbTelemetryClusterType::DockerCompose, + TELEMETRY_CLUSTER_TYPE_KUBERNETES => PbTelemetryClusterType::Kubernetes, + TELEMETRY_CLUSTER_TYPE_SINGLE_NODE => PbTelemetryClusterType::SingleNode, + _ => PbTelemetryClusterType::Unspecified, + } +} /// Url of telemetry backend pub const TELEMETRY_REPORT_URL: &str = "https://telemetry.risingwave.dev/api/v2/report"; @@ -159,10 +182,53 @@ pub fn current_timestamp() -> u64 { .as_secs() } +pub fn report_scarf_enabled() -> bool { + telemetry_env_enabled() + && !matches!( + telemetry_cluster_type_from_env_var(), + PbTelemetryClusterType::CloudHosted + ) +} + +// impl logic to report to Scarf service, containing RW version and deployment platform +pub async fn report_to_scarf() { + let request_url = format!( + "https://risingwave.gateway.scarf.sh/telemetry/{}/{}", + RW_VERSION, + System::name().unwrap_or_default() + ); + // keep trying every 1h until success + loop { + let res = reqwest::get(&request_url).await; + if let Ok(res) = res { + if res.status().is_success() { + break; + } + } + tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await; + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn test_enable_scarf() { + std::env::set_var(TELEMETRY_ENV_ENABLE, "true"); + + // setting env var to `Hosted` should disable scarf + std::env::set_var(TELEMETRY_CLUSTER_TYPE, TELEMETRY_CLUSTER_TYPE_HOSTED); + assert!(!report_scarf_enabled()); + + // setting env var to `DockerCompose` should enable scarf + std::env::set_var( + TELEMETRY_CLUSTER_TYPE, + TELEMETRY_CLUSTER_TYPE_DOCKER_COMPOSE, + ); + assert!(report_scarf_enabled()); + } + #[test] fn test_system_data_new() { let system_data = SystemData::new(); diff --git a/src/common/src/test_utils/rand_bitmap.rs b/src/common/src/test_utils/rand_bitmap.rs index 8e76381b54556..a6e8c325dc513 100644 --- a/src/common/src/test_utils/rand_bitmap.rs +++ b/src/common/src/test_utils/rand_bitmap.rs @@ -16,7 +16,7 @@ use itertools::Itertools; use rand::seq::SliceRandom; use rand::SeedableRng; -use crate::buffer::{Bitmap, BitmapBuilder}; +use crate::bitmap::{Bitmap, BitmapBuilder}; pub fn gen_rand_bitmap(num_bits: usize, count_ones: usize, seed: u64) -> Bitmap { if count_ones == num_bits { diff --git a/src/common/src/test_utils/rand_stream_chunk.rs b/src/common/src/test_utils/rand_stream_chunk.rs index 34cbe5627a54b..6c066bfbbd7ca 100644 --- a/src/common/src/test_utils/rand_stream_chunk.rs +++ b/src/common/src/test_utils/rand_stream_chunk.rs @@ -16,7 +16,7 @@ use rand::{Rng, SeedableRng}; use crate::array::stream_chunk::Op; use crate::array::{ArrayBuilder, ArrayImpl, I64ArrayBuilder}; -use crate::buffer::Bitmap; +use crate::bitmap::Bitmap; pub fn gen_legal_stream_chunk( bitmap: &Bitmap, diff --git a/src/common/src/types/cow.rs b/src/common/src/types/cow.rs new file mode 100644 index 0000000000000..88eebfe1e4c2a --- /dev/null +++ b/src/common/src/types/cow.rs @@ -0,0 +1,80 @@ +// Copyright 2024 RisingWave Labs +// +// 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 super::{Datum, DatumRef, ToDatumRef, ToOwnedDatum}; + +/// 🐮 A borrowed [`DatumRef`] or an owned [`Datum`]. +/// +/// We do not use [`std::borrow::Cow`] because it requires the borrowed variant +/// to be a reference, whereas what we have is a [`DatumRef`] with a lifetime. +/// +/// # Usage +/// +/// Generally, you don't need to match on the variants of `DatumCow` to access +/// the underlying datum. Instead, you can... +/// +/// - call [`to_datum_ref`](ToDatumRef::to_datum_ref) to get a borrowed +/// [`DatumRef`] without any allocation, which can be used to append to an +/// array builder or to encode into the storage representation, +/// +/// - call [`to_owned_datum`](ToOwnedDatum::to_owned_datum) to get an owned +/// [`Datum`] with potentially an allocation, which can be used to store in a +/// struct without lifetime constraints. +#[derive(Debug, Clone)] +pub enum DatumCow<'a> { + Borrowed(DatumRef<'a>), + Owned(Datum), +} + +impl PartialEq for DatumCow<'_> { + fn eq(&self, other: &Self) -> bool { + self.to_datum_ref() == other.to_datum_ref() + } +} +impl Eq for DatumCow<'_> {} + +impl From for DatumCow<'_> { + fn from(datum: Datum) -> Self { + DatumCow::Owned(datum) + } +} + +impl<'a> From> for DatumCow<'a> { + fn from(datum: DatumRef<'a>) -> Self { + DatumCow::Borrowed(datum) + } +} + +impl ToDatumRef for DatumCow<'_> { + fn to_datum_ref(&self) -> DatumRef<'_> { + match self { + DatumCow::Borrowed(datum) => *datum, + DatumCow::Owned(datum) => datum.to_datum_ref(), + } + } +} + +impl ToOwnedDatum for DatumCow<'_> { + fn to_owned_datum(self) -> Datum { + match self { + DatumCow::Borrowed(datum) => datum.to_owned_datum(), + DatumCow::Owned(datum) => datum, + } + } +} + +impl DatumCow<'_> { + /// Equivalent to `DatumCow::Owned(Datum::None)`. + pub const NULL: DatumCow<'static> = DatumCow::Owned(None); +} diff --git a/src/common/src/types/datetime.rs b/src/common/src/types/datetime.rs index 9199dd41b6e5b..bac96b6c1dea5 100644 --- a/src/common/src/types/datetime.rs +++ b/src/common/src/types/datetime.rs @@ -21,8 +21,10 @@ use std::io::Write; use std::str::FromStr; use bytes::{Bytes, BytesMut}; -use chrono::{Datelike, Days, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Weekday}; -use postgres_types::{accepts, to_sql_checked, IsNull, ToSql, Type}; +use chrono::{ + DateTime, Datelike, Days, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Weekday, +}; +use postgres_types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type}; use risingwave_common_estimate_size::ZeroHeapSize; use thiserror::Error; @@ -88,6 +90,20 @@ impl ToSql for Date { } } +impl<'a> FromSql<'a> for Date { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> std::result::Result> { + let instant = NaiveDate::from_sql(ty, raw)?; + Ok(Self::from(instant)) + } + + fn accepts(ty: &Type) -> bool { + matches!(*ty, Type::DATE) + } +} + impl ToSql for Time { accepts!(TIME); @@ -105,6 +121,20 @@ impl ToSql for Time { } } +impl<'a> FromSql<'a> for Time { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> std::result::Result> { + let instant = NaiveTime::from_sql(ty, raw)?; + Ok(Self::from(instant)) + } + + fn accepts(ty: &Type) -> bool { + matches!(*ty, Type::TIME) + } +} + impl ToSql for Timestamp { accepts!(TIMESTAMP); @@ -122,6 +152,20 @@ impl ToSql for Timestamp { } } +impl<'a> FromSql<'a> for Timestamp { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> std::result::Result> { + let instant = NaiveDateTime::from_sql(ty, raw)?; + Ok(Self::from(instant)) + } + + fn accepts(ty: &Type) -> bool { + matches!(*ty, Type::TIMESTAMP) + } +} + /// Parse a date from varchar. /// /// # Example @@ -528,7 +572,8 @@ impl Time { impl Timestamp { pub fn with_secs_nsecs(secs: i64, nsecs: u32) -> Result { Ok(Timestamp::new({ - NaiveDateTime::from_timestamp_opt(secs, nsecs) + DateTime::from_timestamp(secs, nsecs) + .map(|t| t.naive_utc()) .ok_or_else(|| InvalidParamsError::datetime(secs, nsecs))? })) } @@ -536,12 +581,12 @@ impl Timestamp { /// Although `Timestamp` takes 12 bytes, we drop 4 bytes in protobuf encoding. pub fn to_protobuf(self, output: &mut T) -> ArrayResult { output - .write(&(self.0.timestamp_micros()).to_be_bytes()) + .write(&(self.0.and_utc().timestamp_micros()).to_be_bytes()) .map_err(Into::into) } pub fn get_timestamp_nanos(&self) -> i64 { - self.0.timestamp_nanos_opt().unwrap() + self.0.and_utc().timestamp_nanos_opt().unwrap() } pub fn with_millis(timestamp_millis: i64) -> Result { @@ -557,7 +602,7 @@ impl Timestamp { } pub fn from_timestamp_uncheck(secs: i64, nsecs: u32) -> Self { - Self::new(NaiveDateTime::from_timestamp_opt(secs, nsecs).unwrap()) + Self::new(DateTime::from_timestamp(secs, nsecs).unwrap().naive_utc()) } /// Truncate the timestamp to the precision of microseconds. @@ -837,10 +882,10 @@ mod tests { Timestamp::from_str("2022-08-03 10:34:02").unwrap() ); let ts = Timestamp::from_str("0001-11-15 07:35:40.999999").unwrap(); - assert_eq!(ts.0.timestamp_micros(), -62108094259000001); + assert_eq!(ts.0.and_utc().timestamp_micros(), -62108094259000001); let ts = Timestamp::from_str("1969-12-31 23:59:59.999999").unwrap(); - assert_eq!(ts.0.timestamp_micros(), -1); + assert_eq!(ts.0.and_utc().timestamp_micros(), -1); // invalid datetime Date::from_str("1999-01-08AA").unwrap_err(); diff --git a/src/common/src/types/decimal.rs b/src/common/src/types/decimal.rs index b6031a1665540..b38dbb4822e68 100644 --- a/src/common/src/types/decimal.rs +++ b/src/common/src/types/decimal.rs @@ -13,14 +13,15 @@ // limitations under the License. use std::fmt::Debug; -use std::io::{Read, Write}; +use std::io::{Cursor, Read, Write}; use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; +use byteorder::{BigEndian, ReadBytesExt}; use bytes::{BufMut, Bytes, BytesMut}; use num_traits::{ CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedSub, Num, One, Zero, }; -use postgres_types::{accepts, to_sql_checked, IsNull, ToSql, Type}; +use postgres_types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type}; use risingwave_common_estimate_size::ZeroHeapSize; use rust_decimal::prelude::FromStr; use rust_decimal::{Decimal as RustDecimal, Error, MathematicalOps as _, RoundingStrategy}; @@ -145,6 +146,28 @@ impl ToSql for Decimal { } } +impl<'a> FromSql<'a> for Decimal { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + let mut rdr = Cursor::new(raw); + let _n_digits = rdr.read_u16::()?; + let _weight = rdr.read_i16::()?; + let sign = rdr.read_u16::()?; + match sign { + 0xC000 => Ok(Self::NaN), + 0xD000 => Ok(Self::PositiveInf), + 0xF000 => Ok(Self::NegativeInf), + _ => RustDecimal::from_sql(ty, raw).map(Self::Normalized), + } + } + + fn accepts(ty: &Type) -> bool { + matches!(*ty, Type::NUMERIC) + } +} + macro_rules! impl_convert_int { ($T:ty) => { impl core::convert::From<$T> for Decimal { diff --git a/src/common/src/types/from_sql.rs b/src/common/src/types/from_sql.rs new file mode 100644 index 0000000000000..ba1d49892c602 --- /dev/null +++ b/src/common/src/types/from_sql.rs @@ -0,0 +1,66 @@ +// Copyright 2024 RisingWave Labs +// +// 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 postgres_types::{FromSql, Type}; +use risingwave_common::types::{ + Date, Interval, JsonbVal, ScalarImpl, Time, Timestamp, Timestamptz, +}; + +impl<'a> FromSql<'a> for ScalarImpl { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + Ok(match *ty { + Type::BOOL => ScalarImpl::from(bool::from_sql(ty, raw)?), + Type::INT2 => ScalarImpl::from(i16::from_sql(ty, raw)?), + Type::INT4 => ScalarImpl::from(i32::from_sql(ty, raw)?), + Type::INT8 => ScalarImpl::from(i64::from_sql(ty, raw)?), + Type::FLOAT4 => ScalarImpl::from(f32::from_sql(ty, raw)?), + Type::FLOAT8 => ScalarImpl::from(f64::from_sql(ty, raw)?), + Type::DATE => ScalarImpl::from(Date::from_sql(ty, raw)?), + Type::TIME => ScalarImpl::from(Time::from_sql(ty, raw)?), + Type::TIMESTAMP => ScalarImpl::from(Timestamp::from_sql(ty, raw)?), + Type::TIMESTAMPTZ => ScalarImpl::from(Timestamptz::from_sql(ty, raw)?), + Type::JSONB => ScalarImpl::from(JsonbVal::from_sql(ty, raw)?), + Type::INTERVAL => ScalarImpl::from(Interval::from_sql(ty, raw)?), + Type::BYTEA => ScalarImpl::from(Vec::::from_sql(ty, raw)?.into_boxed_slice()), + Type::VARCHAR | Type::TEXT => ScalarImpl::from(String::from_sql(ty, raw)?), + // Serial, Int256, Struct, List and Decimal are not supported here + // Note: The Decimal type is specially handled in the `ScalarAdapter`. + _ => bail_not_implemented!("the postgres decoding for {ty} is unsupported"), + }) + } + + fn accepts(ty: &Type) -> bool { + matches!( + *ty, + Type::BOOL + | Type::INT2 + | Type::INT4 + | Type::INT8 + | Type::FLOAT4 + | Type::FLOAT8 + | Type::DATE + | Type::TIME + | Type::TIMESTAMP + | Type::TIMESTAMPTZ + | Type::JSONB + | Type::INTERVAL + | Type::BYTEA + | Type::VARCHAR + | Type::TEXT + ) + } +} diff --git a/src/common/src/types/interval.rs b/src/common/src/types/interval.rs index 4436181059d78..495561561e937 100644 --- a/src/common/src/types/interval.rs +++ b/src/common/src/types/interval.rs @@ -22,15 +22,12 @@ use std::sync::LazyLock; use byteorder::{BigEndian, NetworkEndian, ReadBytesExt, WriteBytesExt}; use bytes::BytesMut; -use chrono::Timelike; use num_traits::{CheckedAdd, CheckedNeg, CheckedSub, Zero}; -use postgres_types::{to_sql_checked, FromSql}; +use postgres_types::to_sql_checked; use regex::Regex; -use risingwave_common_estimate_size::ZeroHeapSize; use risingwave_pb::data::PbInterval; use rust_decimal::prelude::Decimal; -use super::to_binary::ToBinary; use super::*; /// Every interval can be represented by a `Interval`. diff --git a/src/common/src/types/jsonb.rs b/src/common/src/types/jsonb.rs index 522ec788d8646..642b363a8c67e 100644 --- a/src/common/src/types/jsonb.rs +++ b/src/common/src/types/jsonb.rs @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt; +use std::fmt::{self, Write}; use std::hash::Hash; -use bytes::Buf; +use bytes::{Buf, BufMut, BytesMut}; use jsonbb::{Value, ValueRef}; +use postgres_types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type}; use risingwave_common_estimate_size::EstimateSize; use super::{Datum, IntoOrdered, ListValue, ScalarImpl, StructRef, ToOwnedDatum, F64}; @@ -238,10 +239,15 @@ impl<'a> JsonbRef<'a> { } /// Returns a jsonb `null` value. - pub fn null() -> Self { + pub const fn null() -> Self { Self(ValueRef::Null) } + /// Returns a value for empty string. + pub const fn empty_string() -> Self { + Self(ValueRef::String("")) + } + /// Returns true if this is a jsonb `null`. pub fn is_jsonb_null(&self) -> bool { self.0.is_null() @@ -539,3 +545,38 @@ impl std::io::Write for FmtToIoUnchecked { Ok(()) } } + +impl ToSql for JsonbVal { + accepts!(JSONB); + + to_sql_checked!(); + + fn to_sql( + &self, + _ty: &Type, + out: &mut BytesMut, + ) -> Result> + where + Self: Sized, + { + out.put_u8(1); + write!(out, "{}", self.0).unwrap(); + Ok(IsNull::No) + } +} + +impl<'a> FromSql<'a> for JsonbVal { + fn from_sql( + _ty: &Type, + mut raw: &'a [u8], + ) -> Result> { + if raw.is_empty() || raw.get_u8() != 1 { + return Err("invalid jsonb encoding".into()); + } + Ok(JsonbVal::from(Value::from_text(raw)?)) + } + + fn accepts(ty: &Type) -> bool { + matches!(*ty, Type::JSONB) + } +} diff --git a/src/common/src/types/mod.rs b/src/common/src/types/mod.rs index 9364f438cfacf..69e727cbde655 100644 --- a/src/common/src/types/mod.rs +++ b/src/common/src/types/mod.rs @@ -17,7 +17,6 @@ // NOTE: When adding or modifying data types, remember to update the type matrix in // src/expr/macro/src/types.rs -use std::convert::TryFrom; use std::fmt::Debug; use std::hash::Hash; use std::str::FromStr; @@ -46,9 +45,11 @@ use crate::{ for_all_scalar_variants, for_all_type_pairs, }; +mod cow; mod datetime; mod decimal; mod fields; +mod from_sql; mod interval; mod jsonb; mod macros; @@ -72,6 +73,7 @@ mod with_data_type; pub use fields::Fields; pub use risingwave_fields_derive::Fields; +pub use self::cow::DatumCow; pub use self::datetime::{Date, Time, Timestamp}; pub use self::decimal::{Decimal, PowError as DecimalPowError}; pub use self::interval::{test_utils, DateTimeField, Interval, IntervalDisplay}; @@ -999,8 +1001,8 @@ impl ScalarRefImpl<'_> { Self::Interval(v) => v.serialize(ser)?, Self::Date(v) => v.0.num_days_from_ce().serialize(ser)?, Self::Timestamp(v) => { - v.0.timestamp().serialize(&mut *ser)?; - v.0.timestamp_subsec_nanos().serialize(ser)?; + v.0.and_utc().timestamp().serialize(&mut *ser)?; + v.0.and_utc().timestamp_subsec_nanos().serialize(ser)?; } Self::Timestamptz(v) => v.serialize(ser)?, Self::Time(v) => { diff --git a/src/common/src/types/num256.rs b/src/common/src/types/num256.rs index 43c97fd8b155a..2acbcf636a037 100644 --- a/src/common/src/types/num256.rs +++ b/src/common/src/types/num256.rs @@ -335,7 +335,6 @@ impl EstimateSize for Int256 { #[cfg(test)] mod tests { use super::*; - use crate::types::F64; macro_rules! check_op { ($t:ty, $lhs:expr, $rhs:expr, [$($op:tt),+]) => { diff --git a/src/common/src/types/ordered.rs b/src/common/src/types/ordered.rs index 77a0588e06672..bb2b0c9f316c1 100644 --- a/src/common/src/types/ordered.rs +++ b/src/common/src/types/ordered.rs @@ -14,7 +14,7 @@ //! `ScalarImpl` and `Datum` wrappers that implement `PartialOrd` and `Ord` with default order type. -use std::cmp::{Ord, Ordering}; +use std::cmp::Ordering; use std::ops::Deref; use risingwave_common_estimate_size::EstimateSize; diff --git a/src/common/src/types/scalar_impl.rs b/src/common/src/types/scalar_impl.rs index db2732ca67c48..d222473fa6bbb 100644 --- a/src/common/src/types/scalar_impl.rs +++ b/src/common/src/types/scalar_impl.rs @@ -15,8 +15,6 @@ use std::hash::Hasher; use super::*; -use crate::array::list_array::{ListRef, ListValue}; -use crate::array::struct_array::{StructRef, StructValue}; use crate::{dispatch_scalar_ref_variants, dispatch_scalar_variants, for_all_native_types}; /// `ScalarPartialOrd` allows comparison between `Scalar` and `ScalarRef`. diff --git a/src/common/src/types/timestamptz.rs b/src/common/src/types/timestamptz.rs index 29369d36e9757..feafee6e212bf 100644 --- a/src/common/src/types/timestamptz.rs +++ b/src/common/src/types/timestamptz.rs @@ -19,7 +19,7 @@ use std::str::FromStr; use bytes::{Bytes, BytesMut}; use chrono::{DateTime, Datelike, TimeZone, Utc}; use chrono_tz::Tz; -use postgres_types::{accepts, to_sql_checked, IsNull, ToSql, Type}; +use postgres_types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type}; use risingwave_common_estimate_size::ZeroHeapSize; use serde::{Deserialize, Serialize}; @@ -51,6 +51,20 @@ impl ToSql for Timestamptz { } } +impl<'a> FromSql<'a> for Timestamptz { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + let instant = DateTime::::from_sql(ty, raw)?; + Ok(Self::from(instant)) + } + + fn accepts(ty: &Type) -> bool { + matches!(*ty, Type::TIMESTAMPTZ) + } +} + impl ToBinary for Timestamptz { fn to_binary_with_type(&self, _ty: &DataType) -> super::to_binary::Result> { let instant = self.to_datetime_utc(); @@ -130,6 +144,7 @@ impl Timestamptz { pub fn lookup_time_zone(time_zone: &str) -> std::result::Result { Tz::from_str_insensitive(time_zone) + .map_err(|_| format!("'{time_zone}' is not a valid timezone")) } pub fn from_protobuf(timestamp_micros: i64) -> ArrayResult { diff --git a/src/common/src/types/to_sql.rs b/src/common/src/types/to_sql.rs index 71957b3bf35c8..3ece8a574c450 100644 --- a/src/common/src/types/to_sql.rs +++ b/src/common/src/types/to_sql.rs @@ -15,11 +15,11 @@ use std::error::Error; use bytes::BytesMut; -use postgres_types::{accepts, to_sql_checked, IsNull, ToSql, Type}; +use postgres_types::{to_sql_checked, IsNull, ToSql, Type}; -use crate::types::{JsonbRef, ScalarRefImpl}; +use crate::types::ScalarImpl; -impl ToSql for ScalarRefImpl<'_> { +impl ToSql for ScalarImpl { to_sql_checked!(); fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result> @@ -27,25 +27,23 @@ impl ToSql for ScalarRefImpl<'_> { Self: Sized, { match self { - ScalarRefImpl::Int16(v) => v.to_sql(ty, out), - ScalarRefImpl::Int32(v) => v.to_sql(ty, out), - ScalarRefImpl::Int64(v) => v.to_sql(ty, out), - ScalarRefImpl::Serial(v) => v.to_sql(ty, out), - ScalarRefImpl::Float32(v) => v.to_sql(ty, out), - ScalarRefImpl::Float64(v) => v.to_sql(ty, out), - ScalarRefImpl::Utf8(v) => v.to_sql(ty, out), - ScalarRefImpl::Bool(v) => v.to_sql(ty, out), - ScalarRefImpl::Decimal(v) => v.to_sql(ty, out), - ScalarRefImpl::Interval(v) => v.to_sql(ty, out), - ScalarRefImpl::Date(v) => v.to_sql(ty, out), - ScalarRefImpl::Timestamp(v) => v.to_sql(ty, out), - ScalarRefImpl::Timestamptz(v) => v.to_sql(ty, out), - ScalarRefImpl::Time(v) => v.to_sql(ty, out), - ScalarRefImpl::Bytea(v) => v.to_sql(ty, out), - ScalarRefImpl::Jsonb(_) // jsonbb::Value doesn't implement ToSql yet - | ScalarRefImpl::Int256(_) - | ScalarRefImpl::Struct(_) - | ScalarRefImpl::List(_) => { + ScalarImpl::Int16(v) => v.to_sql(ty, out), + ScalarImpl::Int32(v) => v.to_sql(ty, out), + ScalarImpl::Int64(v) => v.to_sql(ty, out), + ScalarImpl::Serial(v) => v.to_sql(ty, out), + ScalarImpl::Float32(v) => v.to_sql(ty, out), + ScalarImpl::Float64(v) => v.to_sql(ty, out), + ScalarImpl::Utf8(v) => v.to_sql(ty, out), + ScalarImpl::Bool(v) => v.to_sql(ty, out), + ScalarImpl::Decimal(v) => v.to_sql(ty, out), + ScalarImpl::Interval(v) => v.to_sql(ty, out), + ScalarImpl::Date(v) => v.to_sql(ty, out), + ScalarImpl::Timestamp(v) => v.to_sql(ty, out), + ScalarImpl::Timestamptz(v) => v.to_sql(ty, out), + ScalarImpl::Time(v) => v.to_sql(ty, out), + ScalarImpl::Bytea(v) => (&**v).to_sql(ty, out), + ScalarImpl::Jsonb(v) => v.to_sql(ty, out), + ScalarImpl::Int256(_) | ScalarImpl::Struct(_) | ScalarImpl::List(_) => { bail_not_implemented!("the postgres encoding for {ty} is unsupported") } } @@ -59,18 +57,3 @@ impl ToSql for ScalarRefImpl<'_> { true } } - -impl ToSql for JsonbRef<'_> { - accepts!(JSONB); - - to_sql_checked!(); - - fn to_sql(&self, _: &Type, out: &mut BytesMut) -> Result> - where - Self: Sized, - { - let buf = self.value_serialize(); - out.extend(buf); - Ok(IsNull::No) - } -} diff --git a/src/common/src/types/to_text.rs b/src/common/src/types/to_text.rs index d7722aade977b..ca140b93c37b7 100644 --- a/src/common/src/types/to_text.rs +++ b/src/common/src/types/to_text.rs @@ -47,15 +47,18 @@ pub trait ToText { /// - `ScalarRefImpl::Float32` -> `DataType::Float32` /// - `ScalarRefImpl::Float64` -> `DataType::Float64` /// - `ScalarRefImpl::Decimal` -> `DataType::Decimal` - /// - `ScalarRefImpl::Boolean` -> `DataType::Boolean` + /// - `ScalarRefImpl::Bool` -> `DataType::Boolean` /// - `ScalarRefImpl::Utf8` -> `DataType::Varchar` /// - `ScalarRefImpl::Bytea` -> `DataType::Bytea` /// - `ScalarRefImpl::Date` -> `DataType::Date` /// - `ScalarRefImpl::Time` -> `DataType::Time` /// - `ScalarRefImpl::Timestamp` -> `DataType::Timestamp` + /// - `ScalarRefImpl::Timestamptz` -> `DataType::Timestamptz` /// - `ScalarRefImpl::Interval` -> `DataType::Interval` + /// - `ScalarRefImpl::Jsonb` -> `DataType::Jsonb` /// - `ScalarRefImpl::List` -> `DataType::List` /// - `ScalarRefImpl::Struct` -> `DataType::Struct` + /// - `ScalarRefImpl::Serial` -> `DataType::Serial` fn to_text(&self) -> String { let mut s = String::new(); self.write(&mut s).unwrap(); diff --git a/src/common/src/util/chunk_coalesce.rs b/src/common/src/util/chunk_coalesce.rs index 5cdeb8026a0f1..40017e9bc003c 100644 --- a/src/common/src/util/chunk_coalesce.rs +++ b/src/common/src/util/chunk_coalesce.rs @@ -228,6 +228,10 @@ impl DataChunkBuilder { self.buffered_count } + pub fn can_append(&self, count: usize) -> bool { + self.buffered_count + count <= self.batch_size + } + pub fn num_columns(&self) -> usize { self.data_types.len() } @@ -459,13 +463,13 @@ mod tests { let mut left_array_builder = DataType::Int32.create_array_builder(5); for v in [1, 2, 3, 4, 5] { - left_array_builder.append(&Some(ScalarImpl::Int32(v))); + left_array_builder.append(Some(ScalarImpl::Int32(v))); } let left_arrays = [left_array_builder.finish()]; let mut right_array_builder = DataType::Int64.create_array_builder(5); for v in [5, 4, 3, 2, 1] { - right_array_builder.append(&Some(ScalarImpl::Int64(v))); + right_array_builder.append(Some(ScalarImpl::Int64(v))); } let right_arrays = [right_array_builder.finish()]; diff --git a/src/common/src/util/epoch.rs b/src/common/src/util/epoch.rs index a067c689f669d..56dbdf6c54da9 100644 --- a/src/common/src/util/epoch.rs +++ b/src/common/src/util/epoch.rs @@ -174,10 +174,11 @@ impl EpochPair { Self::new(curr, curr - EPOCH_INC_MIN_STEP_FOR_TEST) } } -/// As most unit tests initializ a new epoch from a random value (e.g. 1, 2, 233 etc.), but the correct epoch in the system is a u64 with the last `EPOCH_AVAILABLE_BITS` bits set to 0. + +/// As most unit tests initialize a new epoch from a random value (e.g. 1, 2, 233 etc.), but the correct epoch in the system is a u64 with the last `EPOCH_AVAILABLE_BITS` bits set to 0. /// This method is to turn a a random epoch into a well shifted value. -pub const fn test_epoch(value: u64) -> u64 { - value << EPOCH_AVAILABLE_BITS +pub const fn test_epoch(value_millis: u64) -> u64 { + value_millis << EPOCH_AVAILABLE_BITS } /// There are numerous operations in our system's unit tests that involve incrementing or decrementing the epoch. diff --git a/src/common/src/util/memcmp_encoding.rs b/src/common/src/util/memcmp_encoding.rs index 5709d42d226f8..5a5ad598093af 100644 --- a/src/common/src/util/memcmp_encoding.rs +++ b/src/common/src/util/memcmp_encoding.rs @@ -333,15 +333,12 @@ pub fn decode_row( mod tests { use std::ops::Neg; - use itertools::Itertools; use rand::thread_rng; use super::*; - use crate::array::{DataChunk, ListValue, StructValue}; - use crate::row::{OwnedRow, RowExt}; - use crate::types::{DataType, FloatExt, ScalarImpl, F32}; - use crate::util::iter_util::ZipEqFast; - use crate::util::sort_util::{ColumnOrder, OrderType}; + use crate::array::{ListValue, StructValue}; + use crate::row::RowExt; + use crate::types::FloatExt; #[test] fn test_memcomparable() { @@ -546,7 +543,7 @@ mod tests { use rand::seq::SliceRandom; fn serialize(f: F32) -> MemcmpEncoded { - encode_value(&Some(ScalarImpl::from(f)), OrderType::default()).unwrap() + encode_value(Some(ScalarImpl::from(f)), OrderType::default()).unwrap() } fn deserialize(data: MemcmpEncoded) -> F32 { diff --git a/src/common/src/util/mod.rs b/src/common/src/util/mod.rs index c8027ad46e381..20dac5906c91d 100644 --- a/src/common/src/util/mod.rs +++ b/src/common/src/util/mod.rs @@ -41,3 +41,4 @@ pub mod stream_graph_visitor; pub mod tracing; pub mod value_encoding; pub mod worker_util; +pub use tokio_util; diff --git a/src/common/src/util/panic.rs b/src/common/src/util/panic.rs index 43db76df4f768..517a040e03b09 100644 --- a/src/common/src/util/panic.rs +++ b/src/common/src/util/panic.rs @@ -71,8 +71,8 @@ pub fn is_catching_unwind() -> bool { } #[cfg(all(test, not(madsim)))] +#[expect(clippy::disallowed_methods)] mod tests { - #![allow(clippy::disallowed_methods)] use rusty_fork::rusty_fork_test; diff --git a/src/common/src/util/prost.rs b/src/common/src/util/prost.rs index 4a4f846f543a5..8145a37a8a202 100644 --- a/src/common/src/util/prost.rs +++ b/src/common/src/util/prost.rs @@ -12,7 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::btree_map::Entry; +use std::fmt::{Display, Formatter}; +use std::ops::Deref; + use risingwave_pb::batch_plan; +use risingwave_pb::monitor_service::StackTraceResponse; +use tracing::warn; pub trait TypeUrl { fn type_url() -> &'static str; @@ -23,3 +29,104 @@ impl TypeUrl for batch_plan::ExchangeNode { "type.googleapis.com/plan.ExchangeNode" } } + +pub struct StackTraceResponseOutput<'a>(&'a StackTraceResponse); + +impl<'a> Deref for StackTraceResponseOutput<'a> { + type Target = StackTraceResponse; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<'a> Display for StackTraceResponseOutput<'a> { + fn fmt(&self, s: &mut Formatter<'_>) -> std::fmt::Result { + if !self.actor_traces.is_empty() { + writeln!(s, "--- Actor Traces ---")?; + for (actor_id, trace) in &self.actor_traces { + writeln!(s, ">> Actor {}", *actor_id)?; + writeln!(s, "{trace}")?; + } + } + if !self.rpc_traces.is_empty() { + let _ = writeln!(s, "--- RPC Traces ---"); + for (name, trace) in &self.rpc_traces { + writeln!(s, ">> RPC {name}")?; + writeln!(s, "{trace}")?; + } + } + if !self.compaction_task_traces.is_empty() { + writeln!(s, "--- Compactor Traces ---")?; + for (name, trace) in &self.compaction_task_traces { + writeln!(s, ">> Compaction Task {name}")?; + writeln!(s, "{trace}")?; + } + } + + if !self.inflight_barrier_traces.is_empty() { + writeln!(s, "--- Inflight Barrier Traces ---")?; + for (name, trace) in &self.inflight_barrier_traces { + writeln!(s, ">> Barrier {name}")?; + writeln!(s, "{trace}")?; + } + } + + writeln!(s, "\n\n--- Barrier Worker States ---")?; + for (worker_id, state) in &self.barrier_worker_state { + writeln!(s, ">> Worker {worker_id}")?; + writeln!(s, "{state}\n")?; + } + + if !self.jvm_stack_traces.is_empty() { + writeln!(s, "\n\n--- JVM Stack Traces ---")?; + for (worker_id, state) in &self.jvm_stack_traces { + writeln!(s, ">> Worker {worker_id}")?; + writeln!(s, "{state}\n")?; + } + } + + Ok(()) + } +} + +#[easy_ext::ext(StackTraceResponseExt)] +impl StackTraceResponse { + pub fn merge_other(&mut self, b: StackTraceResponse) { + self.actor_traces.extend(b.actor_traces); + self.rpc_traces.extend(b.rpc_traces); + self.compaction_task_traces.extend(b.compaction_task_traces); + self.inflight_barrier_traces + .extend(b.inflight_barrier_traces); + for (worker_id, worker_state) in b.barrier_worker_state { + match self.barrier_worker_state.entry(worker_id) { + Entry::Occupied(_entry) => { + warn!( + worker_id, + worker_state, "duplicate barrier worker state. skipped" + ); + } + Entry::Vacant(entry) => { + entry.insert(worker_state); + } + } + } + for (worker_id, worker_state) in b.jvm_stack_traces { + match self.jvm_stack_traces.entry(worker_id) { + Entry::Occupied(_entry) => { + warn!( + worker_id, + worker_state, "duplicate jvm stack trace. skipped" + ); + } + Entry::Vacant(entry) => { + entry.insert(worker_state); + } + } + } + } + + pub fn output(&self) -> StackTraceResponseOutput<'_> { + StackTraceResponseOutput(self) + } +} diff --git a/src/common/src/util/scan_range.rs b/src/common/src/util/scan_range.rs index 19e0cb50b83c6..fd056f1790444 100644 --- a/src/common/src/util/scan_range.rs +++ b/src/common/src/util/scan_range.rs @@ -15,8 +15,8 @@ use std::ops::{Bound, RangeBounds}; use paste::paste; -use risingwave_pb::batch_plan::scan_range::Bound as BoundPb; -use risingwave_pb::batch_plan::ScanRange as ScanRangePb; +use risingwave_pb::batch_plan::scan_range::Bound as PbBound; +use risingwave_pb::batch_plan::ScanRange as PbScanRange; use super::value_encoding::serialize_datum; use crate::hash::table_distribution::TableDistribution; @@ -24,20 +24,20 @@ use crate::hash::VirtualNode; use crate::types::{Datum, ScalarImpl}; use crate::util::value_encoding::serialize_datum_into; -/// See also [`ScanRangePb`] +/// See also [`PbScanRange`] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ScanRange { pub eq_conds: Vec, pub range: (Bound, Bound), } -fn bound_to_proto(bound: &Bound) -> Option { +fn bound_to_proto(bound: &Bound) -> Option { match bound { - Bound::Included(literal) => Some(BoundPb { + Bound::Included(literal) => Some(PbBound { value: serialize_datum(Some(literal)), inclusive: true, }), - Bound::Excluded(literal) => Some(BoundPb { + Bound::Excluded(literal) => Some(PbBound { value: serialize_datum(Some(literal)), inclusive: false, }), @@ -46,8 +46,8 @@ fn bound_to_proto(bound: &Bound) -> Option { } impl ScanRange { - pub fn to_protobuf(&self) -> ScanRangePb { - ScanRangePb { + pub fn to_protobuf(&self) -> PbScanRange { + PbScanRange { eq_conds: self .eq_conds .iter() diff --git a/src/common/src/util/sort_util.rs b/src/common/src/util/sort_util.rs index 015222136d382..ff4bde652496d 100644 --- a/src/common/src/util/sort_util.rs +++ b/src/common/src/util/sort_util.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::cmp::{Ord, Ordering}; +use std::cmp::Ordering; use std::fmt; use std::sync::Arc; @@ -574,13 +574,11 @@ pub fn cmp_rows(lhs: impl Row, rhs: impl Row, order_types: &[OrderType]) -> Orde #[cfg(test)] mod tests { - use std::cmp::Ordering; - use itertools::Itertools; use super::*; - use crate::array::{DataChunk, ListValue, StructValue}; - use crate::row::{OwnedRow, Row}; + use crate::array::{ListValue, StructValue}; + use crate::row::OwnedRow; use crate::types::{DataType, Datum, ScalarImpl}; #[test] diff --git a/src/common/src/util/stream_graph_visitor.rs b/src/common/src/util/stream_graph_visitor.rs index 81f1189693c0d..553ede90bcb77 100644 --- a/src/common/src/util/stream_graph_visitor.rs +++ b/src/common/src/util/stream_graph_visitor.rs @@ -36,8 +36,8 @@ where visit_inner(stream_node, &mut f) } -/// A utility for to accessing the [`StreamNode`]. The returned bool is used to determine whether the access needs to continue. -pub fn visit_stream_node_cont(stream_node: &mut StreamNode, mut f: F) +/// A utility for to accessing the [`StreamNode`] mutably. The returned bool is used to determine whether the access needs to continue. +pub fn visit_stream_node_cont_mut(stream_node: &mut StreamNode, mut f: F) where F: FnMut(&mut StreamNode) -> bool, { @@ -56,6 +56,26 @@ where visit_inner(stream_node, &mut f) } +/// A utility for to accessing the [`StreamNode`] immutably. The returned bool is used to determine whether the access needs to continue. +pub fn visit_stream_node_cont(stream_node: &StreamNode, mut f: F) +where + F: FnMut(&StreamNode) -> bool, +{ + fn visit_inner(stream_node: &StreamNode, f: &mut F) + where + F: FnMut(&StreamNode) -> bool, + { + if !f(stream_node) { + return; + } + for input in &stream_node.input { + visit_inner(input, f); + } + } + + visit_inner(stream_node, &mut f) +} + /// A utility for visiting and mutating the [`NodeBody`] of the [`StreamNode`]s in a /// [`StreamFragment`] recursively. pub fn visit_fragment(fragment: &mut StreamFragment, f: F) @@ -200,12 +220,6 @@ pub fn visit_stream_node_tables_inner( optional!(node.table, "Sink") } - // Subscription - NodeBody::Subscription(node) => { - // A Subscription should have a log store - optional!(node.log_store_table, "Subscription") - } - // Now NodeBody::Now(node) => { always!(node.state_table, "Now"); diff --git a/src/common/src/util/value_encoding/column_aware_row_encoding.rs b/src/common/src/util/value_encoding/column_aware_row_encoding.rs index 28fcb65c4d8db..1bfbc0c926410 100644 --- a/src/common/src/util/value_encoding/column_aware_row_encoding.rs +++ b/src/common/src/util/value_encoding/column_aware_row_encoding.rs @@ -26,7 +26,6 @@ use bitflags::bitflags; use super::*; use crate::catalog::ColumnId; -use crate::row::Row; // deprecated design of have a Width to represent number of datum // may be considered should `ColumnId` representation be optimized diff --git a/src/common/src/util/value_encoding/mod.rs b/src/common/src/util/value_encoding/mod.rs index 57e21c3782aa0..a3da88911ad9a 100644 --- a/src/common/src/util/value_encoding/mod.rs +++ b/src/common/src/util/value_encoding/mod.rs @@ -20,7 +20,7 @@ use either::{for_both, Either}; use enum_as_inner::EnumAsInner; use risingwave_pb::data::PbDatum; -use crate::array::{ArrayImpl, ListRef, ListValue, StructRef, StructValue}; +use crate::array::ArrayImpl; use crate::row::{Row, RowDeserializer as BasicDeserializer}; use crate::types::*; @@ -214,9 +214,11 @@ fn serialize_scalar(value: ScalarRefImpl<'_>, buf: &mut impl BufMut) { ScalarRefImpl::Decimal(v) => serialize_decimal(&v, buf), ScalarRefImpl::Interval(v) => serialize_interval(&v, buf), ScalarRefImpl::Date(v) => serialize_date(v.0.num_days_from_ce(), buf), - ScalarRefImpl::Timestamp(v) => { - serialize_timestamp(v.0.timestamp(), v.0.timestamp_subsec_nanos(), buf) - } + ScalarRefImpl::Timestamp(v) => serialize_timestamp( + v.0.and_utc().timestamp(), + v.0.and_utc().timestamp_subsec_nanos(), + buf, + ), ScalarRefImpl::Timestamptz(v) => buf.put_i64_le(v.timestamp_micros()), ScalarRefImpl::Time(v) => { serialize_time(v.0.num_seconds_from_midnight(), v.0.nanosecond(), buf) diff --git a/src/common/src/util/worker_util.rs b/src/common/src/util/worker_util.rs index 893cd95ecbbbb..80ecd3b822536 100644 --- a/src/common/src/util/worker_util.rs +++ b/src/common/src/util/worker_util.rs @@ -12,23 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; - -use risingwave_pb::common::WorkerNode; - -use crate::hash::ParallelUnitId; - pub type WorkerNodeId = u32; - -pub fn get_pu_to_worker_mapping(nodes: &[WorkerNode]) -> HashMap { - let mut pu_to_worker = HashMap::new(); - - for node in nodes { - for pu in &node.parallel_units { - let res = pu_to_worker.insert(pu.id, node.clone()); - assert!(res.is_none(), "duplicate parallel unit id"); - } - } - - pu_to_worker -} diff --git a/src/common/src/vnode_mapping/vnode_placement.rs b/src/common/src/vnode_mapping/vnode_placement.rs index 49f45d66512eb..8c48ab286455c 100644 --- a/src/common/src/vnode_mapping/vnode_placement.rs +++ b/src/common/src/vnode_mapping/vnode_placement.rs @@ -13,46 +13,48 @@ // limitations under the License. use std::collections::{HashMap, HashSet, LinkedList, VecDeque}; +use std::ops::BitOrAssign; use itertools::Itertools; use num_integer::Integer; +use risingwave_common::hash::WorkerSlotId; use risingwave_pb::common::WorkerNode; -use crate::buffer::{Bitmap, BitmapBuilder}; -use crate::hash::{ParallelUnitId, ParallelUnitMapping, VirtualNode}; +use crate::bitmap::{Bitmap, BitmapBuilder}; +use crate::hash::{VirtualNode, WorkerSlotMapping}; /// Calculate a new vnode mapping, keeping locality and balance on a best effort basis. /// The strategy is similar to `rebalance_actor_vnode` used in meta node, but is modified to /// consider `max_parallelism` too. pub fn place_vnode( - hint_pu_mapping: Option<&ParallelUnitMapping>, - new_workers: &[WorkerNode], + hint_worker_slot_mapping: Option<&WorkerSlotMapping>, + workers: &[WorkerNode], max_parallelism: Option, -) -> Option { - // Get all serving parallel units from all available workers, grouped by worker id and ordered - // by parallel unit id in each group. - let mut new_pus: LinkedList<_> = new_workers +) -> Option { + // Get all serving worker slots from all available workers, grouped by worker id and ordered + // by worker slot id in each group. + let mut worker_slots: LinkedList<_> = workers .iter() .filter(|w| w.property.as_ref().map_or(false, |p| p.is_serving)) .sorted_by_key(|w| w.id) - .map(|w| w.parallel_units.clone().into_iter().sorted_by_key(|p| p.id)) + .map(|w| (0..w.parallelism()).map(|idx| WorkerSlotId::new(w.id, idx))) .collect(); - // Set serving parallelism to the minimum of total number of parallel units, specified + // Set serving parallelism to the minimum of total number of worker slots, specified // `max_parallelism` and total number of virtual nodes. let serving_parallelism = std::cmp::min( - new_pus.iter().map(|pus| pus.len()).sum(), + worker_slots.iter().map(|slots| slots.len()).sum(), std::cmp::min(max_parallelism.unwrap_or(usize::MAX), VirtualNode::COUNT), ); - // Select `serving_parallelism` parallel units in a round-robin fashion, to distribute workload + // Select `serving_parallelism` worker slots in a round-robin fashion, to distribute workload // evenly among workers. - let mut selected_pu_ids = Vec::new(); - while !new_pus.is_empty() { - new_pus - .extract_if(|ps| { - if let Some(p) = ps.next() { - selected_pu_ids.push(p.id); + let mut selected_slots = Vec::new(); + while !worker_slots.is_empty() { + worker_slots + .extract_if(|slots| { + if let Some(slot) = slots.next() { + selected_slots.push(slot); false } else { true @@ -60,57 +62,61 @@ pub fn place_vnode( }) .for_each(drop); } - selected_pu_ids.drain(serving_parallelism..); - let selected_pu_id_set: HashSet = selected_pu_ids.iter().cloned().collect(); - if selected_pu_id_set.is_empty() { + selected_slots.drain(serving_parallelism..); + let selected_slots_set: HashSet = selected_slots.iter().cloned().collect(); + if selected_slots_set.is_empty() { return None; } - // Calculate balance for each selected parallel unit. Initially, each parallel unit is assigned + // Calculate balance for each selected worker slot. Initially, each worker slot is assigned // no vnodes. Thus its negative balance means that many vnodes should be assigned to it later. - // `is_temp` is a mark for a special temporary parallel unit, only to simplify implementation. + // `is_temp` is a mark for a special temporary worker slot, only to simplify implementation. #[derive(Debug)] struct Balance { - pu_id: ParallelUnitId, + slot: WorkerSlotId, balance: i32, builder: BitmapBuilder, is_temp: bool, } - let (expected, mut remain) = VirtualNode::COUNT.div_rem(&selected_pu_ids.len()); - let mut balances: HashMap = HashMap::default(); - for pu_id in &selected_pu_ids { + + let (expected, mut remain) = VirtualNode::COUNT.div_rem(&selected_slots.len()); + let mut balances: HashMap = HashMap::default(); + + for slot in &selected_slots { let mut balance = Balance { - pu_id: *pu_id, + slot: *slot, balance: -(expected as i32), builder: BitmapBuilder::zeroed(VirtualNode::COUNT), is_temp: false, }; + if remain > 0 { balance.balance -= 1; remain -= 1; } - balances.insert(*pu_id, balance); + balances.insert(*slot, balance); } - // Now to maintain affinity, if a hint has been provided via `hint_pu_mapping`, follow + // Now to maintain affinity, if a hint has been provided via `hint_worker_slot_mapping`, follow // that mapping to adjust balances. - let mut temp_pu = Balance { - pu_id: 0, // This id doesn't matter for `temp_pu`. It's distinguishable via `is_temp`. + let mut temp_slot = Balance { + slot: WorkerSlotId::new(0u32, usize::MAX), /* This id doesn't matter for `temp_slot`. It's distinguishable via `is_temp`. */ balance: 0, builder: BitmapBuilder::zeroed(VirtualNode::COUNT), is_temp: true, }; - match hint_pu_mapping { - Some(hint_pu_mapping) => { - for (vnode, pu_id) in hint_pu_mapping.iter_with_vnode() { - let b = if selected_pu_id_set.contains(&pu_id) { - // Assign vnode to the same parallel unit as hint. - balances.get_mut(&pu_id).unwrap() + match hint_worker_slot_mapping { + Some(hint_worker_slot_mapping) => { + for (vnode, worker_slot) in hint_worker_slot_mapping.iter_with_vnode() { + let b = if selected_slots_set.contains(&worker_slot) { + // Assign vnode to the same worker slot as hint. + balances.get_mut(&worker_slot).unwrap() } else { - // Assign vnode that doesn't belong to any parallel unit to `temp_pu` + // Assign vnode that doesn't belong to any worker slot to `temp_slot` // temporarily. They will be reassigned later. - &mut temp_pu + &mut temp_slot }; + b.balance += 1; b.builder.set(vnode.to_index(), true); } @@ -118,31 +124,33 @@ pub fn place_vnode( None => { // No hint is provided, assign all vnodes to `temp_pu`. for vnode in VirtualNode::all() { - temp_pu.balance += 1; - temp_pu.builder.set(vnode.to_index(), true); + temp_slot.balance += 1; + temp_slot.builder.set(vnode.to_index(), true); } } } - // The final step is to move vnodes from parallel units with positive balance to parallel units - // with negative balance, until all parallel units are of 0 balance. - // A double-ended queue with parallel units ordered by balance in descending order is consumed: - // 1. Peek 2 parallel units from front and back. + // The final step is to move vnodes from worker slots with positive balance to worker slots + // with negative balance, until all worker slots are of 0 balance. + // A double-ended queue with worker slots ordered by balance in descending order is consumed: + // 1. Peek 2 worker slots from front and back. // 2. It any of them is of 0 balance, pop it and go to step 1. // 3. Otherwise, move vnodes from front to back. let mut balances: VecDeque<_> = balances .into_values() - .chain(std::iter::once(temp_pu)) + .chain(std::iter::once(temp_slot)) .sorted_by_key(|b| b.balance) .rev() .collect(); - let mut results: HashMap = HashMap::default(); + + let mut results: HashMap = HashMap::default(); + while !balances.is_empty() { if balances.len() == 1 { let single = balances.pop_front().unwrap(); assert_eq!(single.balance, 0); if !single.is_temp { - results.insert(single.pu_id, single.builder.finish()); + results.insert(single.slot, single.builder.finish()); } break; } @@ -166,32 +174,42 @@ pub fn place_vnode( if src.balance != 0 { balances.push_front(src); } else if !src.is_temp { - results.insert(src.pu_id, src.builder.finish()); + results.insert(src.slot, src.builder.finish()); } if dst.balance != 0 { balances.push_back(dst); } else if !dst.is_temp { - results.insert(dst.pu_id, dst.builder.finish()); + results.insert(dst.slot, dst.builder.finish()); } } - Some(ParallelUnitMapping::from_bitmaps(&results)) + let mut worker_result = HashMap::new(); + + for (worker_slot, bitmap) in results { + worker_result + .entry(worker_slot) + .or_insert(BitmapBuilder::zeroed(VirtualNode::COUNT).finish()) + .bitor_assign(&bitmap); + } + + Some(WorkerSlotMapping::from_bitmaps(&worker_result)) } #[cfg(test)] mod tests { use std::collections::HashMap; + use risingwave_common::hash::WorkerSlotMapping; use risingwave_pb::common::worker_node::Property; use risingwave_pb::common::{ParallelUnit, WorkerNode}; - use crate::hash::{ParallelUnitId, ParallelUnitMapping, VirtualNode}; + use crate::hash::{ParallelUnitId, VirtualNode}; use crate::vnode_mapping::vnode_placement::place_vnode; - #[test] fn test_place_vnode() { assert_eq!(VirtualNode::COUNT, 256); + let mut pu_id_counter: ParallelUnitId = 0; let mut pu_to_worker: HashMap = Default::default(); let serving_property = Property { @@ -216,13 +234,13 @@ mod tests { results }; - let count_same_vnode_mapping = |pm1: &ParallelUnitMapping, pm2: &ParallelUnitMapping| { - assert_eq!(pm1.len(), 256); - assert_eq!(pm2.len(), 256); + let count_same_vnode_mapping = |wm1: &WorkerSlotMapping, wm2: &WorkerSlotMapping| { + assert_eq!(wm1.len(), 256); + assert_eq!(wm2.len(), 256); let mut count: usize = 0; for idx in 0..VirtualNode::COUNT { let vnode = VirtualNode::from_index(idx); - if pm1.get(vnode) == pm2.get(vnode) { + if wm1.get(vnode) == wm2.get(vnode) { count += 1; } } @@ -235,29 +253,32 @@ mod tests { property: Some(serving_property.clone()), ..Default::default() }; + assert!( place_vnode(None, &[worker_1.clone()], Some(0)).is_none(), "max_parallelism should >= 0" ); - let re_pu_mapping_2 = place_vnode(None, &[worker_1.clone()], None).unwrap(); - assert_eq!(re_pu_mapping_2.iter_unique().count(), 1); + let re_worker_mapping_2 = place_vnode(None, &[worker_1.clone()], None).unwrap(); + assert_eq!(re_worker_mapping_2.iter_unique().count(), 1); + let worker_2 = WorkerNode { id: 2, parallel_units: gen_pus_for_worker(2, 50, &mut pu_to_worker), property: Some(serving_property.clone()), ..Default::default() }; - let re_pu_mapping = place_vnode( - Some(&re_pu_mapping_2), + + let re_worker_mapping = place_vnode( + Some(&re_worker_mapping_2), &[worker_1.clone(), worker_2.clone()], None, ) .unwrap(); - assert_eq!(re_pu_mapping.iter_unique().count(), 51); + assert_eq!(re_worker_mapping.iter_unique().count(), 51); // 1 * 256 + 0 -> 51 * 5 + 1 - let score = count_same_vnode_mapping(&re_pu_mapping_2, &re_pu_mapping); + let score = count_same_vnode_mapping(&re_worker_mapping_2, &re_worker_mapping); assert!(score >= 5); let worker_3 = WorkerNode { @@ -267,7 +288,7 @@ mod tests { ..Default::default() }; let re_pu_mapping_2 = place_vnode( - Some(&re_pu_mapping), + Some(&re_worker_mapping), &[worker_1.clone(), worker_2.clone(), worker_3.clone()], None, ) @@ -276,7 +297,7 @@ mod tests { // limited by total pu number assert_eq!(re_pu_mapping_2.iter_unique().count(), 111); // 51 * 5 + 1 -> 111 * 2 + 34 - let score = count_same_vnode_mapping(&re_pu_mapping_2, &re_pu_mapping); + let score = count_same_vnode_mapping(&re_pu_mapping_2, &re_worker_mapping); assert!(score >= (2 + 50 * 2)); let re_pu_mapping = place_vnode( Some(&re_pu_mapping_2), diff --git a/src/compute/src/lib.rs b/src/compute/src/lib.rs index c3d82dddfde94..7a3d2be65d1df 100644 --- a/src/compute/src/lib.rs +++ b/src/compute/src/lib.rs @@ -38,6 +38,7 @@ use risingwave_common::config::{AsyncStackTraceOption, MetricLevel, OverrideConf use risingwave_common::util::meta_addr::MetaAddressStrategy; use risingwave_common::util::resource_util::cpu::total_cpu_available; use risingwave_common::util::resource_util::memory::system_memory_available_bytes; +use risingwave_common::util::tokio_util::sync::CancellationToken; use serde::{Deserialize, Serialize}; /// If `total_memory_bytes` is not specified, the default memory limit will be set to @@ -76,10 +77,6 @@ pub struct ComputeNodeOpts { #[clap(long, env = "RW_META_ADDR", default_value = "http://127.0.0.1:5690")] pub meta_address: MetaAddressStrategy, - /// Payload format of connector sink rpc - #[clap(long, env = "RW_CONNECTOR_RPC_SINK_PAYLOAD_FORMAT")] - pub connector_rpc_sink_payload_format: Option, - /// The path of `risingwave.toml` configuration file. /// /// If empty, default configuration values will be used. @@ -90,6 +87,13 @@ pub struct ComputeNodeOpts { #[clap(long, env = "RW_TOTAL_MEMORY_BYTES", default_value_t = default_total_memory_bytes())] pub total_memory_bytes: usize, + /// Reserved memory for the compute node in bytes. + /// If not set, a portion (default to 30%) for the `total_memory_bytes` will be used as the reserved memory. + /// + /// The total memory compute and storage can use is `total_memory_bytes` - `reserved_memory_bytes`. + #[clap(long, env = "RW_RESERVED_MEMORY_BYTES")] + pub reserved_memory_bytes: Option, + /// The parallelism that the compute node will register to the scheduler of the meta service. #[clap(long, env = "RW_PARALLELISM", default_value_t = default_parallelism())] #[override_opts(if_absent, path = streaming.actor_runtime_worker_threads_num)] @@ -195,7 +199,10 @@ fn validate_opts(opts: &ComputeNodeOpts) { use crate::server::compute_node_serve; /// Start compute node -pub fn start(opts: ComputeNodeOpts) -> Pin + Send>> { +pub fn start( + opts: ComputeNodeOpts, + shutdown: CancellationToken, +) -> Pin + Send>> { // WARNING: don't change the function signature. Making it `async fn` will cause // slow compile in release mode. Box::pin(async move { @@ -215,14 +222,7 @@ pub fn start(opts: ComputeNodeOpts) -> Pin + Send>> .unwrap(); tracing::info!("advertise addr is {}", advertise_addr); - let (join_handle_vec, _shutdown_send) = - compute_node_serve(listen_addr, advertise_addr, opts).await; - - tracing::info!("Server listening at {}", listen_addr); - - for join_handle in join_handle_vec { - join_handle.await.unwrap(); - } + compute_node_serve(listen_addr, advertise_addr, opts, shutdown).await; }) } diff --git a/src/compute/src/memory/config.rs b/src/compute/src/memory/config.rs index ca04f2e411b64..2a6607c268a67 100644 --- a/src/compute/src/memory/config.rs +++ b/src/compute/src/memory/config.rs @@ -12,20 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use foyer::memory::{LfuConfig, LruConfig, S3FifoConfig}; +use foyer::{LfuConfig, LruConfig, S3FifoConfig}; use risingwave_common::config::{ CacheEvictionConfig, EvictionConfig, StorageConfig, StorageMemoryConfig, MAX_BLOCK_CACHE_SHARD_BITS, MAX_META_CACHE_SHARD_BITS, MIN_BUFFER_SIZE_PER_SHARD, }; use risingwave_common::util::pretty_bytes::convert; +use crate::ComputeNodeOpts; + /// The minimal memory requirement of computing tasks in megabytes. pub const MIN_COMPUTE_MEMORY_MB: usize = 512; /// The memory reserved for system usage (stack and code segment of processes, allocation /// overhead, network buffer, etc.) in megabytes. pub const MIN_SYSTEM_RESERVED_MEMORY_MB: usize = 512; -const SYSTEM_RESERVED_MEMORY_PROPORTION: f64 = 0.3; +const RESERVED_MEMORY_LEVELS: [usize; 2] = [16 << 30, usize::MAX]; + +const RESERVED_MEMORY_PROPORTIONS: [f64; 2] = [0.3, 0.2]; const STORAGE_MEMORY_PROPORTION: f64 = 0.3; @@ -38,23 +42,55 @@ const STORAGE_SHARED_BUFFER_MAX_MEMORY_MB: usize = 4096; const STORAGE_META_CACHE_MEMORY_PROPORTION: f64 = 0.35; const STORAGE_SHARED_BUFFER_MEMORY_PROPORTION: f64 = 0.3; +/// The proportion of compute memory used for batch processing. +const COMPUTE_BATCH_MEMORY_PROPORTION: f64 = 0.3; + /// Each compute node reserves some memory for stack and code segment of processes, allocation -/// overhead, network buffer, etc. based on `SYSTEM_RESERVED_MEMORY_PROPORTION`. The reserve memory +/// overhead, network buffer, etc. based on gradient reserve memory proportion. The reserve memory /// size must be larger than `MIN_SYSTEM_RESERVED_MEMORY_MB` -pub fn reserve_memory_bytes(total_memory_bytes: usize) -> (usize, usize) { - if total_memory_bytes < MIN_COMPUTE_MEMORY_MB << 20 { +pub fn reserve_memory_bytes(opts: &ComputeNodeOpts) -> (usize, usize) { + if opts.total_memory_bytes < MIN_COMPUTE_MEMORY_MB << 20 { panic!( "The total memory size ({}) is too small. It must be at least {} MB.", - convert(total_memory_bytes as _), + convert(opts.total_memory_bytes as _), MIN_COMPUTE_MEMORY_MB ); } - let reserved = std::cmp::max( - (total_memory_bytes as f64 * SYSTEM_RESERVED_MEMORY_PROPORTION).ceil() as usize, - MIN_SYSTEM_RESERVED_MEMORY_MB << 20, - ); - (reserved, total_memory_bytes - reserved) + // If `reserved_memory_bytes` is not set, calculate total_memory_bytes based on gradient reserve memory proportion. + let reserved = opts + .reserved_memory_bytes + .unwrap_or_else(|| gradient_reserve_memory_bytes(opts.total_memory_bytes)); + + // Should have at least `MIN_SYSTEM_RESERVED_MEMORY_MB` for reserved memory. + let reserved = std::cmp::max(reserved, MIN_SYSTEM_RESERVED_MEMORY_MB << 20); + + (reserved, opts.total_memory_bytes - reserved) +} + +/// Calculate the reserved memory based on the total memory size. +/// The reserved memory size is calculated based on the following gradient: +/// - 30% of the first 16GB +/// - 20% of the rest +fn gradient_reserve_memory_bytes(total_memory_bytes: usize) -> usize { + let mut total_memory_bytes = total_memory_bytes; + let mut reserved = 0; + for i in 0..RESERVED_MEMORY_LEVELS.len() { + let level_diff = if i == 0 { + RESERVED_MEMORY_LEVELS[0] + } else { + RESERVED_MEMORY_LEVELS[i] - RESERVED_MEMORY_LEVELS[i - 1] + }; + if total_memory_bytes <= level_diff { + reserved += (total_memory_bytes as f64 * RESERVED_MEMORY_PROPORTIONS[i]) as usize; + break; + } else { + reserved += (level_diff as f64 * RESERVED_MEMORY_PROPORTIONS[i]) as usize; + total_memory_bytes -= level_diff; + } + } + + reserved } /// Decide the memory limit for each storage cache. If not specified in `StorageConfig`, memory @@ -63,6 +99,7 @@ pub fn storage_memory_config( non_reserved_memory_bytes: usize, embedded_compactor_enabled: bool, storage_config: &StorageConfig, + is_serving: bool, ) -> StorageMemoryConfig { let (storage_memory_proportion, compactor_memory_proportion) = if embedded_compactor_enabled { (STORAGE_MEMORY_PROPORTION, COMPACTOR_MEMORY_PROPORTION) @@ -104,14 +141,18 @@ pub fn storage_memory_config( * STORAGE_SHARED_BUFFER_MEMORY_PROPORTION) .ceil() as usize) >> 20; - let shared_buffer_capacity_mb = + let mut shared_buffer_capacity_mb = storage_config .shared_buffer_capacity_mb .unwrap_or(std::cmp::min( default_shared_buffer_capacity_mb, STORAGE_SHARED_BUFFER_MAX_MEMORY_MB, )); - if shared_buffer_capacity_mb != default_shared_buffer_capacity_mb { + if is_serving { + default_block_cache_capacity_mb += default_shared_buffer_capacity_mb; + // set 1 to pass internal check + shared_buffer_capacity_mb = 1; + } else if shared_buffer_capacity_mb != default_shared_buffer_capacity_mb { default_block_cache_capacity_mb += default_shared_buffer_capacity_mb; default_block_cache_capacity_mb = default_block_cache_capacity_mb.saturating_sub(shared_buffer_capacity_mb); @@ -202,12 +243,22 @@ pub fn storage_memory_config( }), CacheEvictionConfig::S3Fifo { small_queue_capacity_ratio_in_percent, + ghost_queue_capacity_ratio_in_percent, + small_to_main_freq_threshold, } => EvictionConfig::S3Fifo(S3FifoConfig { small_queue_capacity_ratio: small_queue_capacity_ratio_in_percent.unwrap_or( risingwave_common::config::default::storage::small_queue_capacity_ratio_in_percent( ), ) as f64 / 100.0, + ghost_queue_capacity_ratio: ghost_queue_capacity_ratio_in_percent.unwrap_or( + risingwave_common::config::default::storage::ghost_queue_capacity_ratio_in_percent( + ), + ) as f64 + / 100.0, + small_to_main_freq_threshold: small_to_main_freq_threshold.unwrap_or( + risingwave_common::config::default::storage::small_to_main_freq_threshold(), + ), }), }; @@ -228,23 +279,47 @@ pub fn storage_memory_config( } } +pub fn batch_mem_limit(compute_memory_bytes: usize) -> u64 { + (compute_memory_bytes as f64 * COMPUTE_BATCH_MEMORY_PROPORTION) as u64 +} + #[cfg(test)] mod tests { + use clap::Parser; use risingwave_common::config::StorageConfig; use super::{reserve_memory_bytes, storage_memory_config}; + use crate::ComputeNodeOpts; #[test] fn test_reserve_memory_bytes() { // at least 512 MB - let (reserved, non_reserved) = reserve_memory_bytes(1536 << 20); - assert_eq!(reserved, 512 << 20); - assert_eq!(non_reserved, 1024 << 20); + { + let mut opts = ComputeNodeOpts::parse_from(vec![] as Vec); + opts.total_memory_bytes = 1536 << 20; + let (reserved, non_reserved) = reserve_memory_bytes(&opts); + assert_eq!(reserved, 512 << 20); + assert_eq!(non_reserved, 1024 << 20); + } // reserve based on proportion - let (reserved, non_reserved) = reserve_memory_bytes(10 << 30); - assert_eq!(reserved, 3 << 30); - assert_eq!(non_reserved, 7 << 30); + { + let mut opts = ComputeNodeOpts::parse_from(vec![] as Vec); + opts.total_memory_bytes = 10 << 30; + let (reserved, non_reserved) = reserve_memory_bytes(&opts); + assert_eq!(reserved, 3 << 30); + assert_eq!(non_reserved, 7 << 30); + } + + // reserve based on opts + { + let mut opts = ComputeNodeOpts::parse_from(vec![] as Vec); + opts.total_memory_bytes = 10 << 30; + opts.reserved_memory_bytes = Some(2 << 30); + let (reserved, non_reserved) = reserve_memory_bytes(&opts); + assert_eq!(reserved, 2 << 30); + assert_eq!(non_reserved, 8 << 30); + } } #[test] @@ -253,17 +328,36 @@ mod tests { let total_non_reserved_memory_bytes = 8 << 30; - let memory_config = - storage_memory_config(total_non_reserved_memory_bytes, true, &storage_config); + let memory_config = storage_memory_config( + total_non_reserved_memory_bytes, + true, + &storage_config, + false, + ); assert_eq!(memory_config.block_cache_capacity_mb, 737); assert_eq!(memory_config.meta_cache_capacity_mb, 860); assert_eq!(memory_config.shared_buffer_capacity_mb, 737); assert_eq!(memory_config.compactor_memory_limit_mb, 819); + let memory_config = storage_memory_config( + total_non_reserved_memory_bytes, + false, + &storage_config, + true, + ); + assert_eq!(memory_config.block_cache_capacity_mb, 1966); + assert_eq!(memory_config.meta_cache_capacity_mb, 1146); + assert_eq!(memory_config.shared_buffer_capacity_mb, 1); + assert_eq!(memory_config.compactor_memory_limit_mb, 0); + storage_config.data_file_cache.dir = "data".to_string(); storage_config.meta_file_cache.dir = "meta".to_string(); - let memory_config = - storage_memory_config(total_non_reserved_memory_bytes, true, &storage_config); + let memory_config = storage_memory_config( + total_non_reserved_memory_bytes, + true, + &storage_config, + false, + ); assert_eq!(memory_config.block_cache_capacity_mb, 737); assert_eq!(memory_config.meta_cache_capacity_mb, 860); assert_eq!(memory_config.shared_buffer_capacity_mb, 737); @@ -273,10 +367,23 @@ mod tests { storage_config.cache.meta_cache_capacity_mb = Some(128); storage_config.shared_buffer_capacity_mb = Some(1024); storage_config.compactor_memory_limit_mb = Some(512); - let memory_config = storage_memory_config(0, true, &storage_config); + let memory_config = storage_memory_config(0, true, &storage_config, false); assert_eq!(memory_config.block_cache_capacity_mb, 512); assert_eq!(memory_config.meta_cache_capacity_mb, 128); assert_eq!(memory_config.shared_buffer_capacity_mb, 1024); assert_eq!(memory_config.compactor_memory_limit_mb, 512); } + + #[test] + fn test_gradient_reserve_memory_bytes() { + assert_eq!(super::gradient_reserve_memory_bytes(4 << 30), 1288490188); + assert_eq!(super::gradient_reserve_memory_bytes(8 << 30), 2576980377); + assert_eq!(super::gradient_reserve_memory_bytes(16 << 30), 5153960755); + assert_eq!(super::gradient_reserve_memory_bytes(24 << 30), 6871947673); + assert_eq!(super::gradient_reserve_memory_bytes(32 << 30), 8589934591); + assert_eq!(super::gradient_reserve_memory_bytes(54 << 30), 13314398617); + assert_eq!(super::gradient_reserve_memory_bytes(64 << 30), 15461882265); + assert_eq!(super::gradient_reserve_memory_bytes(100 << 30), 23192823398); + assert_eq!(super::gradient_reserve_memory_bytes(128 << 30), 29205777612); + } } diff --git a/src/compute/src/memory/controller.rs b/src/compute/src/memory/controller.rs index 55d0547249117..2857b0e6b0d66 100644 --- a/src/compute/src/memory/controller.rs +++ b/src/compute/src/memory/controller.rs @@ -12,28 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::atomic::Ordering; use std::sync::Arc; -use risingwave_common::util::epoch::Epoch; +use risingwave_common::sequence::{Sequence, SEQUENCE_GLOBAL}; use risingwave_jni_core::jvm_runtime::load_jvm_memory_stats; use risingwave_stream::executor::monitor::StreamingMetrics; use super::manager::MemoryManagerConfig; -/// Internal state of [`LruWatermarkController`] that saves the state in previous tick. -struct State { - pub used_memory_bytes: usize, - pub lru_watermark_step: u64, - pub lru_watermark_time_ms: u64, +pub enum LruEvictionPolicy { + None, + Stable, + Graceful, + Aggressive, } -impl Default for State { - fn default() -> Self { - let physical_now = Epoch::physical_now(); - Self { - used_memory_bytes: 0, - lru_watermark_step: 0, - lru_watermark_time_ms: physical_now, +impl From for u8 { + fn from(value: LruEvictionPolicy) -> Self { + match value { + LruEvictionPolicy::None => 0, + LruEvictionPolicy::Stable => 1, + LruEvictionPolicy::Graceful => 2, + LruEvictionPolicy::Aggressive => 3, } } } @@ -75,8 +76,11 @@ pub struct LruWatermarkController { threshold_graceful: usize, threshold_aggressive: usize, - /// The state from previous tick - state: State, + eviction_factor_stable: f64, + eviction_factor_graceful: f64, + eviction_factor_aggressive: f64, + + watermark_sequence: Sequence, } impl LruWatermarkController { @@ -91,7 +95,10 @@ impl LruWatermarkController { threshold_stable, threshold_graceful, threshold_aggressive, - state: State::default(), + eviction_factor_stable: config.eviction_factor_stable, + eviction_factor_graceful: config.eviction_factor_graceful, + eviction_factor_aggressive: config.eviction_factor_aggressive, + watermark_sequence: 0, } } } @@ -131,7 +138,7 @@ fn jemalloc_memory_stats() -> MemoryStats { } impl LruWatermarkController { - pub fn tick(&mut self, interval_ms: u32) -> Epoch { + pub fn tick(&mut self) -> Sequence { // NOTE: Be careful! The meaning of `allocated` and `active` differ in JeMalloc and JVM let MemoryStats { allocated: jemalloc_allocated_bytes, @@ -143,87 +150,69 @@ impl LruWatermarkController { let cur_used_memory_bytes = jemalloc_active_bytes + jvm_allocated_bytes; - let last_step = self.state.lru_watermark_step; - let last_used_memory_bytes = self.state.used_memory_bytes; - - // The watermark calculation works in the following way: - // - // 1. When the streaming memory usage is below the graceful threshold, we do not evict - // any caches, and simply reset the step to 0. + // To calculate the total amount of memory that needs to be evicted, we sequentially calculate and accumulate + // the memory amounts exceeding the thresholds for aggressive, graceful, and stable, multiplying each by + // different weights. // - // 2. When the memory usage is between the graceful and aggressive threshold: - // - If the last eviction memory usage decreases after last eviction, we set the eviction step - // to 1. - // - Otherwise, we set the step to last_step + 1. + // (range) : (weight) + // * aggressive ~ inf : evict factor aggressive + // * graceful ~ aggressive : evict factor graceful + // * stable ~ graceful : evict factor stable + // * 0 ~ stable : no eviction // - // 3. When the memory usage exceeds the aggressive threshold: - // - If the memory usage decreases after the last eviction, we set the eviction step to - // last_step. - // - Otherwise, we set the step to last_step * 2. - let mut step = if cur_used_memory_bytes < self.threshold_stable { - // Do not evict if the memory usage is lower than `threshold_stable` - 0 - } else if cur_used_memory_bytes < self.threshold_graceful { - // Evict in equal speed of time before `threshold_graceful` - 1 - } else if cur_used_memory_bytes < self.threshold_aggressive { - // Gracefully evict - if last_used_memory_bytes > cur_used_memory_bytes { - 1 - } else { - last_step + 1 - } - } else if last_used_memory_bytes < cur_used_memory_bytes { - // Aggressively evict - if last_step == 0 { - 2 - } else { - last_step * 2 - } + // Why different weights instead of 1.0? It acts like a penalty factor, used to penalize the system for not + // keeping the memory usage down at the lower thresholds. + let to_evict_bytes = cur_used_memory_bytes.saturating_sub(self.threshold_aggressive) as f64 + * self.eviction_factor_aggressive + + cur_used_memory_bytes + .saturating_sub(self.threshold_graceful) + .min(self.threshold_aggressive - self.threshold_graceful) as f64 + * self.eviction_factor_graceful + + cur_used_memory_bytes + .saturating_sub(self.threshold_stable) + .min(self.threshold_graceful - self.threshold_stable) as f64 + * self.eviction_factor_stable; + let ratio = to_evict_bytes / cur_used_memory_bytes as f64; + let latest_sequence = SEQUENCE_GLOBAL.load(Ordering::Relaxed); + let sequence_diff = + ((latest_sequence - self.watermark_sequence) as f64 * ratio) as Sequence; + self.watermark_sequence = latest_sequence.min(self.watermark_sequence + sequence_diff); + + let policy = if cur_used_memory_bytes > self.threshold_aggressive { + LruEvictionPolicy::Aggressive + } else if cur_used_memory_bytes > self.threshold_graceful { + LruEvictionPolicy::Graceful + } else if cur_used_memory_bytes > self.threshold_stable { + LruEvictionPolicy::Stable } else { - last_step - }; - - let physical_now = Epoch::physical_now(); - let watermark_time_ms = - if (physical_now - self.state.lru_watermark_time_ms) / (interval_ms as u64) < step { - // We do not increase the step and watermark here to prevent a too-advanced watermark. The - // original condition is `prev_watermark_time_ms + interval_ms * step > now`. - step = last_step; - physical_now - } else { - // Increase by (steps * interval) - self.state.lru_watermark_time_ms + (interval_ms as u64 * step) - }; - - self.state = State { - used_memory_bytes: cur_used_memory_bytes, - lru_watermark_step: step, - lru_watermark_time_ms: watermark_time_ms, + LruEvictionPolicy::None }; + self.metrics.lru_latest_sequence.set(latest_sequence as _); self.metrics - .lru_current_watermark_time_ms - .set(watermark_time_ms as i64); - self.metrics.lru_physical_now_ms.set(physical_now as i64); - self.metrics.lru_watermark_step.set(step as i64); + .lru_watermark_sequence + .set(self.watermark_sequence as _); + self.metrics + .lru_eviction_policy + .set(Into::::into(policy) as _); + self.metrics .jemalloc_allocated_bytes - .set(jemalloc_allocated_bytes as i64); + .set(jemalloc_allocated_bytes as _); self.metrics .jemalloc_active_bytes - .set(jemalloc_active_bytes as i64); + .set(jemalloc_active_bytes as _); self.metrics .jemalloc_resident_bytes - .set(jemalloc_resident_bytes as i64); + .set(jemalloc_resident_bytes as _); self.metrics .jemalloc_metadata_bytes - .set(jemalloc_metadata_bytes as i64); + .set(jemalloc_metadata_bytes as _); self.metrics .jvm_allocated_bytes - .set(jvm_allocated_bytes as i64); - self.metrics.jvm_active_bytes.set(jvm_active_bytes as i64); + .set(jvm_allocated_bytes as _); + self.metrics.jvm_active_bytes.set(jvm_active_bytes as _); - Epoch::from_physical_time(watermark_time_ms) + self.watermark_sequence } } diff --git a/src/compute/src/memory/manager.rs b/src/compute/src/memory/manager.rs index 5e8e1b5be54ff..b90624193c703 100644 --- a/src/compute/src/memory/manager.rs +++ b/src/compute/src/memory/manager.rs @@ -16,6 +16,7 @@ use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use risingwave_common::sequence::AtomicSequence; use risingwave_common::system_param::local_manager::SystemParamsReaderRef; use risingwave_common::system_param::reader::SystemParamsRead; use risingwave_stream::executor::monitor::StreamingMetrics; @@ -29,13 +30,17 @@ pub struct MemoryManagerConfig { pub threshold_graceful: f64, pub threshold_stable: f64, + pub eviction_factor_stable: f64, + pub eviction_factor_graceful: f64, + pub eviction_factor_aggressive: f64, + pub metrics: Arc, } /// Compute node uses [`MemoryManager`] to limit the memory usage. pub struct MemoryManager { /// All cached data before the watermark should be evicted. - watermark_epoch: Arc, + watermark_sequence: Arc, metrics: Arc, @@ -52,14 +57,14 @@ impl MemoryManager { tracing::info!("LRU watermark controller: {:?}", &controller); Arc::new(Self { - watermark_epoch: Arc::new(0.into()), + watermark_sequence: Arc::new(0.into()), metrics: config.metrics, controller, }) } - pub fn get_watermark_epoch(&self) -> Arc { - self.watermark_epoch.clone() + pub fn get_watermark_sequence(&self) -> Arc { + self.watermark_sequence.clone() } pub async fn run( @@ -81,20 +86,21 @@ impl MemoryManager { // Wait for a while to check if need eviction. tokio::select! { Ok(_) = system_params_change_rx.changed() => { - let params = system_params_change_rx.borrow().load(); - let new_interval_ms = std::cmp::max(params.barrier_interval_ms(), Self::MIN_TICK_INTERVAL_MS); - if new_interval_ms != interval_ms { - interval_ms = new_interval_ms; - tick_interval = tokio::time::interval(Duration::from_millis(interval_ms as u64)); - tracing::info!("updated MemoryManager interval to {}ms", interval_ms); - } + let params = system_params_change_rx.borrow().load(); + let new_interval_ms = std::cmp::max(params.barrier_interval_ms(), Self::MIN_TICK_INTERVAL_MS); + if new_interval_ms != interval_ms { + interval_ms = new_interval_ms; + tick_interval = tokio::time::interval(Duration::from_millis(interval_ms as u64)); + tracing::info!("updated MemoryManager interval to {}ms", interval_ms); + } } _ = tick_interval.tick() => { - let new_watermark_epoch = self.controller.lock().unwrap().tick(interval_ms); - self.watermark_epoch.store(new_watermark_epoch.0, Ordering::Relaxed); + let new_watermark_sequence = self.controller.lock().unwrap().tick(); + + self.watermark_sequence.store(new_watermark_sequence, Ordering::Relaxed); - self.metrics.lru_runtime_loop_count.inc(); + self.metrics.lru_runtime_loop_count.inc(); } } } diff --git a/src/compute/src/rpc/service/config_service.rs b/src/compute/src/rpc/service/config_service.rs index 97793f71722c9..e827ef176413e 100644 --- a/src/compute/src/rpc/service/config_service.rs +++ b/src/compute/src/rpc/service/config_service.rs @@ -18,7 +18,6 @@ use risingwave_common::error::tonic::ToTonicStatus; use risingwave_pb::compute::config_service_server::ConfigService; use risingwave_pb::compute::{ShowConfigRequest, ShowConfigResponse}; use risingwave_stream::task::LocalStreamManager; -use serde_json; use tonic::{Code, Request, Response, Status}; pub struct ConfigServiceImpl { diff --git a/src/compute/src/rpc/service/monitor_service.rs b/src/compute/src/rpc/service/monitor_service.rs index d2542ca9bd085..a9a41d753ac96 100644 --- a/src/compute/src/rpc/service/monitor_service.rs +++ b/src/compute/src/rpc/service/monitor_service.rs @@ -12,41 +12,58 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::BTreeMap; use std::ffi::CString; use std::fs; use std::path::Path; use std::time::Duration; +use foyer::HybridCache; use itertools::Itertools; use prometheus::core::Collector; use risingwave_common::config::{MetricLevel, ServerConfig}; use risingwave_common_heap_profiling::{AUTO_DUMP_SUFFIX, COLLAPSED_SUFFIX, MANUALLY_DUMP_SUFFIX}; +use risingwave_hummock_sdk::HummockSstableObjectId; +use risingwave_jni_core::jvm_runtime::dump_jvm_stack_traces; use risingwave_pb::monitor_service::monitor_service_server::MonitorService; use risingwave_pb::monitor_service::{ AnalyzeHeapRequest, AnalyzeHeapResponse, BackPressureInfo, GetBackPressureRequest, GetBackPressureResponse, HeapProfilingRequest, HeapProfilingResponse, ListHeapProfilingRequest, ListHeapProfilingResponse, ProfilingRequest, ProfilingResponse, StackTraceRequest, - StackTraceResponse, + StackTraceResponse, TieredCacheTracingRequest, TieredCacheTracingResponse, }; use risingwave_rpc_client::error::ToTonicStatus; use risingwave_storage::hummock::compactor::await_tree_key::Compaction; +use risingwave_storage::hummock::{Block, Sstable, SstableBlockIndex}; use risingwave_stream::executor::monitor::global_streaming_metrics; use risingwave_stream::task::await_tree_key::{Actor, BarrierAwait}; use risingwave_stream::task::LocalStreamManager; use thiserror_ext::AsReport; use tonic::{Code, Request, Response, Status}; +type MetaCache = HybridCache>; +type BlockCache = HybridCache>; + #[derive(Clone)] pub struct MonitorServiceImpl { stream_mgr: LocalStreamManager, server_config: ServerConfig, + meta_cache: Option, + block_cache: Option, } impl MonitorServiceImpl { - pub fn new(stream_mgr: LocalStreamManager, server_config: ServerConfig) -> Self { + pub fn new( + stream_mgr: LocalStreamManager, + server_config: ServerConfig, + meta_cache: Option, + block_cache: Option, + ) -> Self { Self { stream_mgr, server_config, + meta_cache, + block_cache, } } } @@ -99,11 +116,29 @@ impl MonitorService for MonitorServiceImpl { Default::default() }; + let barrier_worker_state = self.stream_mgr.inspect_barrier_state().await?; + + let jvm_stack_traces = match dump_jvm_stack_traces() { + Ok(None) => None, + Err(err) => Some(err.as_report().to_string()), + Ok(Some(stack_traces)) => Some(stack_traces), + }; + Ok(Response::new(StackTraceResponse { actor_traces, rpc_traces, compaction_task_traces, inflight_barrier_traces: barrier_traces, + barrier_worker_state: BTreeMap::from_iter([( + self.stream_mgr.env.worker_id(), + barrier_worker_state, + )]), + jvm_stack_traces: match jvm_stack_traces { + Some(stack_traces) => { + BTreeMap::from_iter([(self.stream_mgr.env.worker_id(), stack_traces)]) + } + None => BTreeMap::new(), + }, })) } @@ -283,6 +318,66 @@ impl MonitorService for MonitorServiceImpl { back_pressure_infos, })) } + + #[cfg_attr(coverage, coverage(off))] + async fn tiered_cache_tracing( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + + tracing::info!("Update tiered cache tracing config: {req:?}"); + + if let Some(cache) = &self.meta_cache { + if req.enable { + cache.enable_tracing(); + } else { + cache.disable_tracing(); + } + let config = cache.tracing_config(); + if let Some(threshold) = req.record_hybrid_insert_threshold_ms { + config.set_record_hybrid_insert_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_get_threshold_ms { + config.set_record_hybrid_get_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_obtain_threshold_ms { + config.set_record_hybrid_obtain_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_remove_threshold_ms { + config.set_record_hybrid_remove_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_fetch_threshold_ms { + config.set_record_hybrid_fetch_threshold(Duration::from_millis(threshold as _)); + } + } + + if let Some(cache) = &self.block_cache { + if req.enable { + cache.enable_tracing(); + } else { + cache.disable_tracing(); + } + let config = cache.tracing_config(); + if let Some(threshold) = req.record_hybrid_insert_threshold_ms { + config.set_record_hybrid_insert_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_get_threshold_ms { + config.set_record_hybrid_get_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_obtain_threshold_ms { + config.set_record_hybrid_obtain_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_remove_threshold_ms { + config.set_record_hybrid_remove_threshold(Duration::from_millis(threshold as _)); + } + if let Some(threshold) = req.record_hybrid_fetch_threshold_ms { + config.set_record_hybrid_fetch_threshold(Duration::from_millis(threshold as _)); + } + } + + Ok(Response::new(TieredCacheTracingResponse::default())) + } } pub use grpc_middleware::*; diff --git a/src/compute/src/server.rs b/src/compute/src/server.rs index bdf47cbd1309f..3b0466b6cc0d1 100644 --- a/src/compute/src/server.rs +++ b/src/compute/src/server.rs @@ -13,19 +13,21 @@ // limitations under the License. use std::net::SocketAddr; -use std::sync::atomic::AtomicU32; use std::sync::Arc; use std::time::Duration; use risingwave_batch::monitor::{ - GLOBAL_BATCH_EXECUTOR_METRICS, GLOBAL_BATCH_MANAGER_METRICS, GLOBAL_BATCH_TASK_METRICS, + GLOBAL_BATCH_EXECUTOR_METRICS, GLOBAL_BATCH_MANAGER_METRICS, GLOBAL_BATCH_SPILL_METRICS, + GLOBAL_BATCH_TASK_METRICS, }; use risingwave_batch::rpc::service::task_service::BatchServiceImpl; +use risingwave_batch::spill::spill_op::SpillOp; use risingwave_batch::task::{BatchEnvironment, BatchManager}; use risingwave_common::config::{ load_config, AsyncStackTraceOption, MetricLevel, StorageMemoryConfig, MAX_CONNECTION_WINDOW_SIZE, STREAM_WINDOW_SIZE, }; +use risingwave_common::lru::init_global_sequencer_args; use risingwave_common::monitor::connection::{RouterExt, TcpConfig}; use risingwave_common::system_param::local_manager::LocalSystemParamsManager; use risingwave_common::system_param::reader::SystemParamsRead; @@ -33,6 +35,7 @@ use risingwave_common::telemetry::manager::TelemetryManager; use risingwave_common::telemetry::telemetry_env_enabled; use risingwave_common::util::addr::HostAddr; use risingwave_common::util::pretty_bytes::convert; +use risingwave_common::util::tokio_util::sync::CancellationToken; use risingwave_common::{GIT_SHA, RW_VERSION}; use risingwave_common_heap_profiling::HeapProfiler; use risingwave_common_service::metrics_manager::MetricsManager; @@ -42,7 +45,6 @@ use risingwave_connector::source::monitor::GLOBAL_SOURCE_METRICS; use risingwave_dml::dml_manager::DmlManager; use risingwave_pb::common::WorkerType; use risingwave_pb::compute::config_service_server::ConfigServiceServer; -use risingwave_pb::connector_service::SinkPayloadFormat; use risingwave_pb::health::health_server::HealthServer; use risingwave_pb::meta::add_worker_node_request::Property; use risingwave_pb::monitor_service::monitor_service_server::MonitorServiceServer; @@ -63,12 +65,13 @@ use risingwave_storage::opts::StorageOpts; use risingwave_storage::StateStoreImpl; use risingwave_stream::executor::monitor::global_streaming_metrics; use risingwave_stream::task::{LocalStreamManager, StreamEnvironment}; -use thiserror_ext::AsReport; use tokio::sync::oneshot::Sender; use tokio::task::JoinHandle; use tower::Layer; -use crate::memory::config::{reserve_memory_bytes, storage_memory_config, MIN_COMPUTE_MEMORY_MB}; +use crate::memory::config::{ + batch_mem_limit, reserve_memory_bytes, storage_memory_config, MIN_COMPUTE_MEMORY_MB, +}; use crate::memory::manager::{MemoryManager, MemoryManagerConfig}; use crate::observer::observer_manager::ComputeObserverNode; use crate::rpc::service::config_service::ConfigServiceImpl; @@ -81,14 +84,16 @@ use crate::telemetry::ComputeTelemetryCreator; use crate::ComputeNodeOpts; /// Bootstraps the compute-node. +/// +/// Returns when the `shutdown` token is triggered. pub async fn compute_node_serve( listen_addr: SocketAddr, advertise_addr: HostAddr, opts: ComputeNodeOpts, -) -> (Vec>, Sender<()>) { + shutdown: CancellationToken, +) { // Load the configuration. let config = load_config(&opts.config_path, &opts); - info!("Starting compute node",); info!("> config: {:?}", config); info!( @@ -101,9 +106,21 @@ pub async fn compute_node_serve( let stream_config = Arc::new(config.streaming.clone()); let batch_config = Arc::new(config.batch.clone()); + // Initialize operator lru cache global sequencer args. + init_global_sequencer_args( + config + .streaming + .developer + .memory_controller_sequence_tls_step, + config + .streaming + .developer + .memory_controller_sequence_tls_lag, + ); + // Register to the cluster. We're not ready to serve until activate is called. let (meta_client, system_params) = MetaClient::register_new( - opts.meta_address, + opts.meta_address.clone(), WorkerType::ComputeNode, &advertise_addr, Property { @@ -122,12 +139,12 @@ pub async fn compute_node_serve( let embedded_compactor_enabled = embedded_compactor_enabled(state_store_url, config.storage.disable_remote_compactor); - let (reserved_memory_bytes, non_reserved_memory_bytes) = - reserve_memory_bytes(opts.total_memory_bytes); + let (reserved_memory_bytes, non_reserved_memory_bytes) = reserve_memory_bytes(&opts); let storage_memory_config = storage_memory_config( non_reserved_memory_bytes, embedded_compactor_enabled, &config.storage, + !opts.role.for_streaming(), ); let storage_memory_bytes = total_storage_memory_limit_bytes(&storage_memory_config); @@ -154,6 +171,7 @@ pub async fn compute_node_serve( let worker_id = meta_client.worker_id(); info!("Assigned worker node id {}", worker_id); + // TODO(shutdown): remove this as there's no need to gracefully shutdown the sub-tasks. let mut sub_tasks: Vec<(JoinHandle<()>, Sender<()>)> = vec![]; // Initialize the metrics subsystem. let source_metrics = Arc::new(GLOBAL_SOURCE_METRICS.clone()); @@ -163,6 +181,7 @@ pub async fn compute_node_serve( let batch_executor_metrics = Arc::new(GLOBAL_BATCH_EXECUTOR_METRICS.clone()); let batch_manager_metrics = Arc::new(GLOBAL_BATCH_MANAGER_METRICS.clone()); let exchange_srv_metrics = Arc::new(GLOBAL_EXCHANGE_SERVICE_METRICS.clone()); + let batch_spill_metrics = Arc::new(GLOBAL_BATCH_SPILL_METRICS.clone()); // Initialize state store. let state_store_metrics = Arc::new(global_hummock_state_store_metrics( @@ -176,8 +195,6 @@ pub async fn compute_node_serve( hummock_metrics.clone(), )); - let mut join_handle_vec = vec![]; - let await_tree_config = match &config.streaming.async_stack_trace { AsyncStackTraceOption::Off => None, c => await_tree::ConfigBuilder::default() @@ -195,6 +212,7 @@ pub async fn compute_node_serve( storage_metrics.clone(), compactor_metrics.clone(), await_tree_config.clone(), + system_params.use_new_object_prefix_strategy(), ) .await .unwrap(); @@ -216,12 +234,6 @@ pub async fn compute_node_serve( )); let compaction_executor = Arc::new(CompactionExecutor::new(Some(1))); - let max_task_parallelism = Arc::new(AtomicU32::new( - (compaction_executor.worker_num() as f32 - * storage_opts.compactor_max_task_multiplier) - .ceil() as u32, - )); - let compactor_context = CompactorContext { storage_opts, sstable_store: storage.sstable_store(), @@ -234,8 +246,6 @@ pub async fn compute_node_serve( await_tree_reg: await_tree_config .clone() .map(new_compaction_await_tree_reg_ref), - running_task_parallelism: Arc::new(AtomicU32::new(0)), - max_task_parallelism, }; let (handle, shutdown_sender) = start_compactor( @@ -272,6 +282,7 @@ pub async fn compute_node_serve( let batch_mgr = Arc::new(BatchManager::new( config.batch.clone(), batch_manager_metrics, + batch_mem_limit(compute_memory_bytes), )); // NOTE: Due to some limits, we use `compute_memory_bytes + storage_memory_bytes` as @@ -295,6 +306,18 @@ pub async fn compute_node_serve( .streaming .developer .memory_controller_threshold_stable, + eviction_factor_stable: config + .streaming + .developer + .memory_controller_eviction_factor_stable, + eviction_factor_graceful: config + .streaming + .developer + .memory_controller_eviction_factor_graceful, + eviction_factor_aggressive: config + .streaming + .developer + .memory_controller_eviction_factor_aggressive, metrics: streaming_metrics.clone(), }); @@ -329,34 +352,16 @@ pub async fn compute_node_serve( client_pool, dml_mgr.clone(), source_metrics.clone(), + batch_spill_metrics.clone(), config.server.metrics_level, ); - info!( - "connector param: payload_format={:?}", - opts.connector_rpc_sink_payload_format - ); - - let connector_params = risingwave_connector::ConnectorParams { - sink_payload_format: match opts.connector_rpc_sink_payload_format.as_deref() { - None | Some("stream_chunk") => SinkPayloadFormat::StreamChunk, - Some("json") => SinkPayloadFormat::Json, - _ => { - unreachable!( - "invalid sink payload format: {:?}. Should be either json or stream_chunk", - opts.connector_rpc_sink_payload_format - ) - } - }, - }; - // Initialize the streaming environment. let stream_env = StreamEnvironment::new( advertise_addr.clone(), - connector_params, stream_config, worker_id, - state_store, + state_store.clone(), dml_mgr, system_params_manager.clone(), source_metrics, @@ -367,7 +372,7 @@ pub async fn compute_node_serve( stream_env.clone(), streaming_metrics.clone(), await_tree_config.clone(), - memory_mgr.get_watermark_epoch(), + memory_mgr.get_watermark_sequence(), ); // Boot the runtime gRPC services. @@ -375,7 +380,20 @@ pub async fn compute_node_serve( let exchange_srv = ExchangeServiceImpl::new(batch_mgr.clone(), stream_mgr.clone(), exchange_srv_metrics); let stream_srv = StreamServiceImpl::new(stream_mgr.clone(), stream_env.clone()); - let monitor_srv = MonitorServiceImpl::new(stream_mgr.clone(), config.server.clone()); + let (meta_cache, block_cache) = if let Some(hummock) = state_store.as_hummock() { + ( + Some(hummock.sstable_store().meta_cache().clone()), + Some(hummock.sstable_store().block_cache().clone()), + ) + } else { + (None, None) + }; + let monitor_srv = MonitorServiceImpl::new( + stream_mgr.clone(), + config.server.clone(), + meta_cache, + block_cache, + ); let config_srv = ConfigServiceImpl::new(batch_mgr, stream_mgr); let health_srv = HealthServiceImpl::new(); @@ -392,63 +410,43 @@ pub async fn compute_node_serve( tracing::info!("Telemetry didn't start due to config"); } - let (shutdown_send, mut shutdown_recv) = tokio::sync::oneshot::channel::<()>(); - let join_handle = tokio::spawn(async move { - tonic::transport::Server::builder() - .initial_connection_window_size(MAX_CONNECTION_WINDOW_SIZE) - .initial_stream_window_size(STREAM_WINDOW_SIZE) - .http2_max_pending_accept_reset_streams(Some( - config.server.grpc_max_reset_stream as usize, - )) - .layer(TracingExtractLayer::new()) - // XXX: unlimit the max message size to allow arbitrary large SQL input. - .add_service(TaskServiceServer::new(batch_srv).max_decoding_message_size(usize::MAX)) - .add_service( - ExchangeServiceServer::new(exchange_srv).max_decoding_message_size(usize::MAX), - ) - .add_service({ - let await_tree_reg = stream_srv.mgr.await_tree_reg().cloned(); - let srv = - StreamServiceServer::new(stream_srv).max_decoding_message_size(usize::MAX); - #[cfg(madsim)] - { - srv - } - #[cfg(not(madsim))] - { - AwaitTreeMiddlewareLayer::new_optional(await_tree_reg).layer(srv) - } - }) - .add_service(MonitorServiceServer::new(monitor_srv)) - .add_service(ConfigServiceServer::new(config_srv)) - .add_service(HealthServer::new(health_srv)) - .monitored_serve_with_shutdown( - listen_addr, - "grpc-compute-node-service", - TcpConfig { - tcp_nodelay: true, - keepalive_duration: None, - }, - async move { - tokio::select! { - _ = tokio::signal::ctrl_c() => {}, - _ = &mut shutdown_recv => { - for (join_handle, shutdown_sender) in sub_tasks { - if let Err(_err) = shutdown_sender.send(()) { - tracing::warn!("Failed to send shutdown"); - continue; - } - if let Err(err) = join_handle.await { - tracing::warn!(error = %err.as_report(), "Failed to join shutdown"); - } - } - }, - } - }, - ) - .await; - }); - join_handle_vec.push(join_handle); + // Clean up the spill directory. + #[cfg(not(madsim))] + SpillOp::clean_spill_directory().await.unwrap(); + + let server = tonic::transport::Server::builder() + .initial_connection_window_size(MAX_CONNECTION_WINDOW_SIZE) + .initial_stream_window_size(STREAM_WINDOW_SIZE) + .http2_max_pending_accept_reset_streams(Some(config.server.grpc_max_reset_stream as usize)) + .layer(TracingExtractLayer::new()) + // XXX: unlimit the max message size to allow arbitrary large SQL input. + .add_service(TaskServiceServer::new(batch_srv).max_decoding_message_size(usize::MAX)) + .add_service(ExchangeServiceServer::new(exchange_srv).max_decoding_message_size(usize::MAX)) + .add_service({ + let await_tree_reg = stream_srv.mgr.await_tree_reg().cloned(); + let srv = StreamServiceServer::new(stream_srv).max_decoding_message_size(usize::MAX); + #[cfg(madsim)] + { + srv + } + #[cfg(not(madsim))] + { + AwaitTreeMiddlewareLayer::new_optional(await_tree_reg).layer(srv) + } + }) + .add_service(MonitorServiceServer::new(monitor_srv)) + .add_service(ConfigServiceServer::new(config_srv)) + .add_service(HealthServer::new(health_srv)) + .monitored_serve_with_shutdown( + listen_addr, + "grpc-compute-node-service", + TcpConfig { + tcp_nodelay: true, + keepalive_duration: None, + }, + shutdown.clone().cancelled_owned(), + ); + let _server_handle = tokio::spawn(server); // Boot metrics service. if config.server.metrics_level > MetricLevel::Disabled { @@ -457,8 +455,16 @@ pub async fn compute_node_serve( // All set, let the meta service know we're ready. meta_client.activate(&advertise_addr).await.unwrap(); + // Wait for the shutdown signal. + shutdown.cancelled().await; + + // TODO(shutdown): gracefully unregister from the meta service (need to cautious since it may + // trigger auto-scaling) - (join_handle_vec, shutdown_send) + // NOTE(shutdown): We can't simply join the tonic server here because it only returns when all + // existing connections are closed, while we have long-running streaming calls that never + // close. From the other side, there's also no need to gracefully shutdown them if we have + // unregistered from the meta service. } /// Check whether the compute node has enough memory to perform computing tasks. Apart from storage, diff --git a/src/compute/src/telemetry.rs b/src/compute/src/telemetry.rs index 143ec51c5bc39..c35a6063ae267 100644 --- a/src/compute/src/telemetry.rs +++ b/src/compute/src/telemetry.rs @@ -33,6 +33,7 @@ impl ComputeTelemetryCreator { #[async_trait::async_trait] impl TelemetryReportCreator for ComputeTelemetryCreator { + #[allow(refining_impl_trait)] async fn create_report( &self, tracking_id: String, diff --git a/src/compute/tests/cdc_tests.rs b/src/compute/tests/cdc_tests.rs index 760c76ca6deba..26f822bae558f 100644 --- a/src/compute/tests/cdc_tests.rs +++ b/src/compute/tests/cdc_tests.rs @@ -25,14 +25,17 @@ use futures::stream::StreamExt; use futures_async_stream::try_stream; use itertools::Itertools; use risingwave_batch::executor::{Executor as BatchExecutor, RowSeqScanExecutor, ScanRange}; -use risingwave_common::array::{Array, ArrayBuilder, DataChunk, Op, StreamChunk, Utf8ArrayBuilder}; +use risingwave_common::array::{ + Array, ArrayBuilder, DataChunk, DataChunkTestExt, Op, StreamChunk, Utf8ArrayBuilder, +}; use risingwave_common::catalog::{ColumnDesc, ColumnId, ConflictBehavior, Field, Schema, TableId}; use risingwave_common::types::{Datum, JsonbVal}; use risingwave_common::util::epoch::{test_epoch, EpochExt}; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_connector::source::cdc::external::mock_external_table::MockExternalTableReader; +use risingwave_connector::source::cdc::external::mysql::MySqlOffset; use risingwave_connector::source::cdc::external::{ - DebeziumOffset, DebeziumSourceOffset, ExternalTableReaderImpl, MySqlOffset, SchemaTableName, + DebeziumOffset, DebeziumSourceOffset, ExternalTableReaderImpl, SchemaTableName, }; use risingwave_connector::source::cdc::DebeziumCdcSplit; use risingwave_connector::source::SplitImpl; @@ -45,8 +48,8 @@ use risingwave_stream::executor::monitor::StreamingMetrics; use risingwave_stream::executor::test_utils::MockSource; use risingwave_stream::executor::{ expect_first_barrier, ActorContext, AddMutation, Barrier, BoxedMessageStream, - CdcBackfillExecutor, Execute, Executor as StreamExecutor, ExecutorInfo, ExternalStorageTable, - MaterializeExecutor, Message, Mutation, StreamExecutorError, + CdcBackfillExecutor, CdcScanOptions, Execute, Executor as StreamExecutor, ExecutorInfo, + ExternalStorageTable, MaterializeExecutor, Message, Mutation, StreamExecutorError, }; // mock upstream binlog offset starting from "1.binlog, pos=0" @@ -167,7 +170,10 @@ async fn test_cdc_backfill() -> StreamResult<()> { MySqlOffset::new(binlog_file.clone(), 10), ]; - let table_name = SchemaTableName::new("mock_table".to_string(), "public".to_string()); + let table_name = SchemaTableName { + schema_name: "public".to_string(), + table_name: "mock_table".to_string(), + }; let table_schema = Schema::new(vec![ Field::with_name(DataType::Int64, "id"), // primary key Field::with_name(DataType::Float64, "price"), @@ -177,15 +183,16 @@ async fn test_cdc_backfill() -> StreamResult<()> { let external_table = ExternalStorageTable::new( TableId::new(1234), table_name, + "mydb".to_string(), ExternalTableReaderImpl::Mock(MockExternalTableReader::new(binlog_watermarks)), table_schema.clone(), table_pk_order_types, table_pk_indices.clone(), - vec![0, 1], ); let actor_id = 0x1a; + // create state table let state_schema = Schema::new(vec![ Field::with_name(DataType::Varchar, "split_id"), Field::with_name(DataType::Int64, "id"), // pk @@ -211,6 +218,11 @@ async fn test_cdc_backfill() -> StreamResult<()> { ) .await; + let output_columns = vec![ + ColumnDesc::named("id", ColumnId::new(1), DataType::Int64), // primary key + ColumnDesc::named("price", ColumnId::new(2), DataType::Float64), + ]; + let cdc_backfill = StreamExecutor::new( ExecutorInfo { schema: table_schema.clone(), @@ -222,11 +234,16 @@ async fn test_cdc_backfill() -> StreamResult<()> { external_table, mock_offset_executor, vec![0, 1], + output_columns, None, Arc::new(StreamingMetrics::unused()), state_table, - 4, // 4 rows in a snapshot chunk - false, + Some(4), // limit a snapshot chunk to have <= 4 rows by rate limit + CdcScanOptions { + disable_backfill: false, + snapshot_interval: 1, + snapshot_batch_size: 4, + }, ) .boxed(), ); @@ -246,9 +263,10 @@ async fn test_cdc_backfill() -> StreamResult<()> { .boxed() .execute(); + // construct upstream chunks let chunk1_payload = vec![ r#"{ "payload": { "before": null, "after": { "id": 1, "price": 10.01}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, - r#"{ "payload": { "before": null, "after": { "id": 2, "price": 2.02}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, + r#"{ "payload": { "before": null, "after": { "id": 2, "price": 22.22}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, r#"{ "payload": { "before": null, "after": { "id": 3, "price": 3.03}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, r#"{ "payload": { "before": null, "after": { "id": 4, "price": 4.04}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, r#"{ "payload": { "before": null, "after": { "id": 5, "price": 5.05}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, @@ -256,6 +274,7 @@ async fn test_cdc_backfill() -> StreamResult<()> { ]; let chunk2_payload = vec![ + r#"{ "payload": { "before": null, "after": { "id": 1, "price": 11.11}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, r#"{ "payload": { "before": null, "after": { "id": 6, "price": 10.08}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, r#"{ "payload": { "before": null, "after": { "id": 199, "price": 40.5}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, r#"{ "payload": { "before": null, "after": { "id": 978, "price": 72.6}, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002"}, "op": "r", "ts_ms": 1695277757017, "transaction": null } }"#, @@ -311,11 +330,22 @@ async fn test_cdc_backfill() -> StreamResult<()> { // ingest data and barrier let interval = Duration::from_millis(10); + + // send a dummy barrier to trigger the backfill, since + // cdc backfill will wait for a barrier before start + curr_epoch.inc_epoch(); + tx.push_barrier(curr_epoch, false); + + // first chunk tx.push_chunk(stream_chunk1); + tokio::time::sleep(interval).await; + + // barrier to trigger emit buffered events curr_epoch.inc_epoch(); tx.push_barrier(curr_epoch, false); + // second chunk tx.push_chunk(stream_chunk2); tokio::time::sleep(interval).await; @@ -352,9 +382,25 @@ async fn test_cdc_backfill() -> StreamResult<()> { None, None, )); + + // check result let mut stream = scan.execute(); while let Some(message) = stream.next().await { - println!("[scan] chunk: {:#?}", message.unwrap()); + let chunk = message.expect("scan a chunk"); + let expect = DataChunk::from_pretty( + "I F + 1 11.11 + 2 22.22 + 3 3.03 + 4 4.04 + 5 5.05 + 6 10.08 + 8 1.0008 + 134 41.7 + 199 40.5 + 978 72.6", + ); + assert_eq!(expect, chunk); } mview_handle.await.unwrap()?; diff --git a/src/compute/tests/integration_tests.rs b/src/compute/tests/integration_tests.rs index f34a2940e9ce5..5c8ec479d3be9 100644 --- a/src/compute/tests/integration_tests.rs +++ b/src/compute/tests/integration_tests.rs @@ -21,14 +21,14 @@ use std::sync::Arc; use futures::stream::StreamExt; use futures_async_stream::try_stream; use itertools::Itertools; -use maplit::{convert_args, hashmap}; +use maplit::{btreemap, convert_args}; use risingwave_batch::error::BatchError; use risingwave_batch::executor::{ BoxedDataChunkStream, BoxedExecutor, DeleteExecutor, Executor as BatchExecutor, InsertExecutor, RowSeqScanExecutor, ScanRange, }; use risingwave_common::array::{Array, DataChunk, F64Array, SerialArray}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::{ ColumnDesc, ColumnId, ConflictBehavior, Field, Schema, TableId, INITIAL_TABLE_VERSION_ID, }; @@ -40,7 +40,6 @@ use risingwave_common::util::epoch::{test_epoch, EpochExt, EpochPair}; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_connector::source::reader::desc::test_utils::create_source_desc_builder; -use risingwave_connector::source::SourceCtrlOpts; use risingwave_dml::dml_manager::DmlManager; use risingwave_hummock_sdk::to_committed_batch_query_epoch; use risingwave_pb::catalog::StreamSourceInfo; @@ -115,7 +114,7 @@ async fn test_table_materialize() -> StreamResult<()> { row_format: PbRowFormatType::Json as i32, ..Default::default() }; - let properties = convert_args!(hashmap!( + let properties = convert_args!(btreemap!( "connector" => "datagen", "fields.v1.min" => "1", "fields.v1.max" => "1000", @@ -173,10 +172,8 @@ async fn test_table_materialize() -> StreamResult<()> { Arc::new(StreamingMetrics::unused()), barrier_rx, system_params_manager.get_params(), - SourceCtrlOpts { - chunk_size: 1024, - rate_limit: None, - }, + None, + false, ) .boxed(), ); diff --git a/src/config/ci-recovery.toml b/src/config/ci-recovery.toml index 95e5834621115..69de86279c1c5 100644 --- a/src/config/ci-recovery.toml +++ b/src/config/ci-recovery.toml @@ -10,5 +10,6 @@ imm_merge_threshold = 2 [system] barrier_interval_ms = 250 +# If this is changed, remember to also change e2e_test/source_inline/pubsub/pubsub.slt checkpoint_frequency = 5 max_concurrent_creating_streaming_jobs = 0 diff --git a/src/config/ci.toml b/src/config/ci.toml index 02e1509546f27..43e9836bd304f 100644 --- a/src/config/ci.toml +++ b/src/config/ci.toml @@ -16,6 +16,12 @@ stream_exchange_concurrent_barriers = 10 [storage] imm_merge_threshold = 2 +[storage.object_store.retry] +streaming_upload_attempt_timeout_ms = 10000 +upload_retry_attempts = 5 +read_attempt_timeout_ms = 16000 +read_retry_attempts = 6 + [system] barrier_interval_ms = 250 checkpoint_frequency = 5 diff --git a/src/config/docs.md b/src/config/docs.md index eacd8039a5d75..f93700f5c5e65 100644 --- a/src/config/docs.md +++ b/src/config/docs.md @@ -8,9 +8,11 @@ This page is automatically generated by `./risedev generate-example-config` |--------|-------------|---------| | distributed_query_limit | This is the max number of queries per sql session. | | | enable_barrier_read | | false | +| enable_spill | Enable the spill out to disk feature for batch queries. | true | | frontend_compute_runtime_worker_threads | frontend compute runtime worker threads | 4 | | mask_worker_temporary_secs | This is the secs used to mask a worker unavailable temporarily. | 30 | | max_batch_queries_per_frontend_node | This is the max number of batch queries per frontend node. | | +| redact_sql_option_keywords | Keywords on which SQL option redaction is based in the query log. A SQL option with a name containing any of these keywords will be redacted. | ["credential", "key", "password", "private", "secret", "token"] | | statement_timeout_in_sec | Timeout for a batch query in seconds. | 3600 | | worker_threads_num | The thread number of the batch task runtime in the compute node. The default value is decided by `tokio`. | | @@ -20,6 +22,8 @@ This page is automatically generated by `./risedev generate-example-config` |--------|-------------|---------| | backend | | "Mem" | | collect_gc_watermark_spin_interval_sec | The spin interval when collecting global GC watermark in hummock. | 5 | +| compact_task_table_size_partition_threshold_high | The threshold of table size in one compact task to decide whether to partition one table into `partition_vnode_count` parts, which belongs to default group and materialized view group. Set it max value of 64-bit number to disable this feature. | 536870912 | +| compact_task_table_size_partition_threshold_low | The threshold of table size in one compact task to decide whether to partition one table into `hybrid_partition_vnode_count` parts, which belongs to default group and materialized view group. Set it max value of 64-bit number to disable this feature. | 134217728 | | compaction_task_max_heartbeat_interval_secs | | 30 | | compaction_task_max_progress_interval_secs | | 600 | | cut_table_size_limit | | 1073741824 | @@ -36,7 +40,7 @@ This page is automatically generated by `./risedev generate-example-config` | event_log_enabled | | true | | full_gc_interval_sec | Interval of automatic hummock full GC. | 86400 | | hummock_version_checkpoint_interval_sec | Interval of hummock version checkpoint. | 30 | -| hybird_partition_vnode_count | | 4 | +| hybrid_partition_vnode_count | Count of partitions of tables in default group and materialized view group. The meta node will decide according to some strategy whether to cut the boundaries of the file according to the vnode alignment. Each partition contains aligned data of `VirtualNode::COUNT / hybrid_partition_vnode_count` consecutive virtual-nodes of one state table. Set it zero to disable this feature. | 4 | | max_heartbeat_interval_secs | Maximum allowed heartbeat interval in seconds. | 60 | | meta_leader_lease_secs | | 30 | | min_delta_log_num_for_hummock_version_checkpoint | The minimum delta log number a new checkpoint should compact, otherwise the checkpoint attempt is rejected. | 10 | @@ -47,14 +51,15 @@ This page is automatically generated by `./risedev generate-example-config` | parallelism_control_batch_size | The number of streaming jobs per scaling operation. | 10 | | parallelism_control_trigger_first_delay_sec | The first delay of parallelism control. | 30 | | parallelism_control_trigger_period_sec | The period of parallelism control trigger. | 10 | -| partition_vnode_count | | 16 | +| partition_vnode_count | Count of partition in split group. Meta will assign this value to every new group when it splits from default-group by automatically. Each partition contains aligned data of `VirtualNode::COUNT / partition_vnode_count` consecutive virtual-nodes of one state table. | 16 | | periodic_compaction_interval_sec | Schedule compaction for all compaction groups with this interval. | 60 | | periodic_space_reclaim_compaction_interval_sec | Schedule `space_reclaim` compaction for all compaction groups with this interval. | 3600 | | periodic_split_compact_group_interval_sec | | 10 | | periodic_tombstone_reclaim_compaction_interval_sec | | 600 | | periodic_ttl_reclaim_compaction_interval_sec | Schedule `ttl_reclaim` compaction for all compaction groups with this interval. | 1800 | +| secret_store_private_key | | [100, 101, 109, 111, 45, 115, 101, 99, 114, 101, 116, 45, 112, 114, 105, 118, 97, 116, 101, 45, 107, 101, 121] | | split_group_size_limit | | 68719476736 | -| table_write_throughput_threshold | | 16777216 | +| table_write_throughput_threshold | The threshold of write throughput to trigger a group split. Increase this configuration value to avoid split too many groups with few data write. | 16777216 | | unrecognized | | | | vacuum_interval_sec | Interval of invoking a vacuum job, to remove stale metadata from meta store and objects from object store. | 30 | | vacuum_spin_interval_ms | The spin interval inside a vacuum job. It avoids the vacuum job monopolizing resources of meta node. | 10 | @@ -73,6 +78,7 @@ This page is automatically generated by `./risedev generate-example-config` | max_bytes_for_level_base | | 536870912 | | max_bytes_for_level_multiplier | | 5 | | max_compaction_bytes | | 2147483648 | +| max_level | | 6 | | max_space_reclaim_bytes | | 536870912 | | max_sub_compaction | | 4 | | sub_level_max_compaction_bytes | | 134217728 | @@ -99,37 +105,40 @@ This page is automatically generated by `./risedev generate-example-config` | cache_refill | | | | check_compaction_result | | false | | compact_iter_recreate_timeout_ms | | 600000 | +| compactor_concurrent_uploading_sst_count | The concurrent uploading number of `SSTables` of buidler | | | compactor_fast_max_compact_delete_ratio | | 40 | | compactor_fast_max_compact_task_size | | 2147483648 | | compactor_iter_max_io_retry_times | | 8 | | compactor_max_sst_key_count | | 2097152 | | compactor_max_sst_size | | 536870912 | -| compactor_max_task_multiplier | Compactor calculates the maximum number of tasks that can be executed on the node based on `worker_num` and `compactor_max_task_multiplier`. `max_pull_task_count` = `worker_num` * `compactor_max_task_multiplier` | 2.5 | +| compactor_max_task_multiplier | Compactor calculates the maximum number of tasks that can be executed on the node based on `worker_num` and `compactor_max_task_multiplier`. `max_pull_task_count` = `worker_num` * `compactor_max_task_multiplier` | 3.0 | | compactor_memory_available_proportion | The percentage of memory available when compactor is deployed separately. `non_reserved_memory_bytes` = `system_memory_available_bytes` * `compactor_memory_available_proportion` | 0.8 | | compactor_memory_limit_mb | | | | data_file_cache | | | | disable_remote_compactor | | false | -| enable_fast_compaction | | false | +| enable_fast_compaction | | true | | high_priority_ratio_in_percent | DEPRECATED: This config will be deprecated in the future version, use `storage.cache.block_cache_eviction.high_priority_ratio_in_percent` with `storage.cache.block_cache_eviction.algorithm = "Lru"` instead. | | | imm_merge_threshold | The threshold for the number of immutable memtables to merge to a new imm. | 0 | | max_concurrent_compaction_task_number | | 16 | | max_prefetch_block_number | max prefetch block number | 16 | | max_preload_io_retry_times | | 3 | | max_preload_wait_time_mill | | 0 | -| max_sub_compaction | Max sub compaction task numbers | 4 | | max_version_pinning_duration_sec | | 10800 | | mem_table_spill_threshold | The spill threshold for mem table. | 4194304 | | meta_cache_capacity_mb | DEPRECATED: This config will be deprecated in the future version, use `storage.cache.meta_cache_capacity_mb` instead. | | | meta_file_cache | | | | min_sst_size_for_streaming_upload | Whether to enable streaming upload for sstable. | 33554432 | -| object_store | | | +| min_sstable_size_mb | | 32 | +| object_store | Object storage configuration 1. General configuration 2. Some special configuration of Backend 3. Retry and timeout configuration | | | prefetch_buffer_capacity_mb | max memory usage for large query | | | share_buffer_compaction_worker_threads_number | Worker threads number of dedicated tokio runtime for share buffer compaction. 0 means use tokio's default value (number of CPU core). | 4 | | share_buffer_upload_concurrency | Number of tasks shared buffer can upload in parallel. | 8 | | share_buffers_sync_parallelism | parallelism while syncing share buffers into L0 SST. Should NOT be 0. | 1 | | shared_buffer_capacity_mb | Maximum shared buffer size, writes attempting to exceed the capacity will stall until there is enough space. | | | shared_buffer_flush_ratio | The shared buffer will start flushing data to object when the ratio of memory usage to the shared buffer capacity exceed such ratio. | 0.800000011920929 | +| shared_buffer_min_batch_flush_size_mb | The minimum total flush size of shared buffer spill. When a shared buffer spilled is trigger, the total flush size across multiple epochs should be at least higher than this size. | 800 | | sstable_id_remote_fetch_number | Number of SST ids fetched from meta per RPC | 10 | +| table_info_statistic_history_times | The window size of table info statistic history. | 240 | | write_conflict_detection_enabled | Whether to enable write conflict detection | true | ## streaming @@ -154,8 +163,10 @@ This page is automatically generated by `./risedev generate-example-config` | checkpoint_frequency | There will be a checkpoint for every n barriers. | 1 | | data_directory | Remote directory for storing data and metadata objects. | | | enable_tracing | Whether to enable distributed tracing. | false | +| license_key | The license key to activate enterprise features. | "" | | max_concurrent_creating_streaming_jobs | Max number of concurrent creating streaming jobs. | 1 | -| parallel_compact_size_mb | | 512 | +| parallel_compact_size_mb | The size of parallel task for one compact/flush job. | 512 | | pause_on_next_bootstrap | Whether to pause all data sources on next bootstrap. | false | | sstable_size_mb | Target size of the Sstable. | 256 | -| state_store | | | +| state_store | URL for the state store | | +| use_new_object_prefix_strategy | Whether to split object prefix. | | diff --git a/src/config/example.toml b/src/config/example.toml index 397bf9a89f69b..3a394eada8645 100644 --- a/src/config/example.toml +++ b/src/config/example.toml @@ -48,10 +48,13 @@ table_write_throughput_threshold = 16777216 min_table_split_write_throughput = 4194304 compaction_task_max_heartbeat_interval_secs = 30 compaction_task_max_progress_interval_secs = 600 -hybird_partition_vnode_count = 4 +hybrid_partition_vnode_count = 4 +compact_task_table_size_partition_threshold_low = 134217728 +compact_task_table_size_partition_threshold_high = 536870912 event_log_enabled = true event_log_channel_max_size = 10 enable_dropped_column_reclaim = false +secret_store_private_key = [100, 101, 109, 111, 45, 115, 101, 99, 114, 101, 116, 45, 112, 114, 105, 118, 97, 116, 101, 45, 107, 101, 121] [meta.compaction_config] max_bytes_for_level_base = 536870912 @@ -69,18 +72,23 @@ max_space_reclaim_bytes = 536870912 level0_max_compact_file_number = 100 tombstone_reclaim_ratio = 40 enable_emergency_picker = true +max_level = 6 [meta.developer] meta_cached_traces_num = 256 meta_cached_traces_memory_limit_bytes = 134217728 meta_enable_trivial_move = true meta_enable_check_task_level_overlap = false +meta_max_trivial_move_task_count_per_loop = 256 +meta_max_get_task_probe_times = 5 [batch] enable_barrier_read = false statement_timeout_in_sec = 3600 frontend_compute_runtime_worker_threads = 4 mask_worker_temporary_secs = 30 +redact_sql_option_keywords = ["credential", "key", "password", "private", "secret", "token"] +enable_spill = true [batch.developer] batch_connector_message_buffer_size = 16 @@ -107,34 +115,43 @@ stream_hash_agg_max_dirty_groups_heap_size = 67108864 stream_memory_controller_threshold_aggressive = 0.9 stream_memory_controller_threshold_graceful = 0.8 stream_memory_controller_threshold_stable = 0.7 +stream_memory_controller_eviction_factor_aggressive = 2.0 +stream_memory_controller_eviction_factor_graceful = 1.5 +stream_memory_controller_eviction_factor_stable = 1.0 +stream_memory_controller_sequence_tls_step = 128 +stream_memory_controller_sequence_tls_lag = 32 stream_enable_arrangement_backfill = true +stream_high_join_amplification_threshold = 2048 +stream_enable_actor_tokio_metrics = false [storage] share_buffers_sync_parallelism = 1 share_buffer_compaction_worker_threads_number = 4 shared_buffer_flush_ratio = 0.800000011920929 +shared_buffer_min_batch_flush_size_mb = 800 imm_merge_threshold = 0 write_conflict_detection_enabled = true max_prefetch_block_number = 16 disable_remote_compactor = false share_buffer_upload_concurrency = 8 -compactor_max_task_multiplier = 2.5 +compactor_max_task_multiplier = 3.0 compactor_memory_available_proportion = 0.8 sstable_id_remote_fetch_number = 10 +min_sstable_size_mb = 32 min_sst_size_for_streaming_upload = 33554432 -max_sub_compaction = 4 max_concurrent_compaction_task_number = 16 max_preload_wait_time_mill = 0 max_version_pinning_duration_sec = 10800 compactor_max_sst_key_count = 2097152 compact_iter_recreate_timeout_ms = 600000 compactor_max_sst_size = 536870912 -enable_fast_compaction = false +enable_fast_compaction = true check_compaction_result = false max_preload_io_retry_times = 3 compactor_fast_max_compact_delete_ratio = 40 compactor_fast_max_compact_task_size = 2147483648 compactor_iter_max_io_retry_times = 8 +table_info_statistic_history_times = 240 mem_table_spill_threshold = 4194304 [storage.cache.block_cache_eviction] @@ -147,30 +164,22 @@ algorithm = "Lru" dir = "" capacity_mb = 1024 file_capacity_mb = 64 -device_align = 4096 -device_io_size = 16384 flushers = 4 reclaimers = 4 recover_concurrency = 8 -lfu_window_to_cache_size_ratio = 1 -lfu_tiny_lru_capacity_ratio = 0.01 insert_rate_limit_mb = 0 -catalog_bits = 6 +indexer_shards = 64 compression = "none" [storage.meta_file_cache] dir = "" capacity_mb = 1024 file_capacity_mb = 64 -device_align = 4096 -device_io_size = 16384 flushers = 4 reclaimers = 4 recover_concurrency = 8 -lfu_window_to_cache_size_ratio = 1 -lfu_tiny_lru_capacity_ratio = 0.01 insert_rate_limit_mb = 0 -catalog_bits = 6 +indexer_shards = 64 compression = "none" [storage.cache_refill] @@ -183,25 +192,41 @@ recent_filter_layers = 6 recent_filter_rotate_interval_ms = 10000 [storage.object_store] -object_store_streaming_read_timeout_ms = 480000 -object_store_streaming_upload_timeout_ms = 480000 -object_store_upload_timeout_ms = 480000 -object_store_read_timeout_ms = 480000 -object_store_set_atomic_write_dir = false +set_atomic_write_dir = false +opendal_upload_concurrency = 8 +opendal_writer_abort_on_err = false + +[storage.object_store.retry] +req_backoff_interval_ms = 1000 +req_backoff_max_delay_ms = 10000 +req_backoff_factor = 2 +upload_attempt_timeout_ms = 8000 +upload_retry_attempts = 3 +streaming_upload_attempt_timeout_ms = 5000 +streaming_upload_retry_attempts = 3 +read_attempt_timeout_ms = 8000 +read_retry_attempts = 3 +streaming_read_attempt_timeout_ms = 3000 +streaming_read_retry_attempts = 3 +metadata_attempt_timeout_ms = 60000 +metadata_retry_attempts = 3 +delete_attempt_timeout_ms = 5000 +delete_retry_attempts = 3 +delete_objects_attempt_timeout_ms = 5000 +delete_objects_retry_attempts = 3 +list_attempt_timeout_ms = 600000 +list_retry_attempts = 3 [storage.object_store.s3] -object_store_keepalive_ms = 600000 -object_store_recv_buffer_size = 2097152 -object_store_nodelay = true -object_store_req_retry_interval_ms = 20 -object_store_req_retry_max_delay_ms = 10000 -object_store_req_retry_max_attempts = 8 +keepalive_ms = 600000 +recv_buffer_size = 2097152 +nodelay = true retry_unknown_service_error = false identity_resolution_timeout_s = 5 [storage.object_store.s3.developer] -object_store_retry_unknown_service_error = false -object_store_retryable_service_error_codes = ["SlowDown", "TooManyRequests"] +retry_unknown_service_error = false +retryable_service_error_codes = ["SlowDown", "TooManyRequests"] use_opendal = false [system] @@ -214,3 +239,4 @@ bloom_false_positive = 0.001 max_concurrent_creating_streaming_jobs = 1 pause_on_next_bootstrap = false enable_tracing = false +license_key = "" diff --git a/src/connector/Cargo.toml b/src/connector/Cargo.toml index e2799e81c5831..9ae4ee7f13022 100644 --- a/src/connector/Cargo.toml +++ b/src/connector/Cargo.toml @@ -15,23 +15,24 @@ normal = ["workspace-hack"] [dependencies] anyhow = "1" -apache-avro = { git = "https://github.com/risingwavelabs/avro", rev = "f33bf6a9d1734d1e23edeb374dc48d26db4b18a5", features = [ - "snappy", - "zstandard", - "bzip", - "xz", -] } +apache-avro = { workspace = true } arrow-array = { workspace = true } +arrow-array-iceberg = { workspace = true } arrow-row = { workspace = true } arrow-schema = { workspace = true } +arrow-schema-iceberg = { workspace = true } arrow-select = { workspace = true } assert_matches = "1" -async-nats = "0.34" +async-compression = { version = "0.4.5", features = ["gzip", "tokio"] } +async-nats = "0.35" async-trait = "0.1" auto_enums = { workspace = true } auto_impl = "1" +await-tree = { workspace = true } aws-config = { workspace = true } aws-credential-types = { workspace = true } +aws-msk-iam-sasl-signer = "1.0.0" +aws-sdk-dynamodb = "1" aws-sdk-kinesis = { workspace = true } aws-sdk-s3 = { workspace = true } aws-smithy-http = { workspace = true } @@ -42,6 +43,7 @@ aws-types = { workspace = true } base64 = "0.22" byteorder = "1" bytes = { version = "1", features = ["serde"] } +cfg-or-panic = "0.2" chrono = { version = "0.4", default-features = false, features = [ "clock", "std", @@ -51,23 +53,29 @@ clickhouse = { git = "https://github.com/risingwavelabs/clickhouse.rs", rev = "d ] } csv = "1.3" deltalake = { workspace = true } -duration-str = "0.7.1" +duration-str = "0.11.2" easy-ext = "1" enum-as-inner = "0.6" futures = { version = "0.3", default-features = false, features = ["alloc"] } futures-async-stream = { workspace = true } gcp-bigquery-client = "0.18.0" glob = "0.3" -google-cloud-pubsub = "0.24" +google-cloud-bigquery = { version = "0.9.0", features = ["auth"] } +google-cloud-gax = "0.17.0" +google-cloud-googleapis = { version = "0.13", features = ["pubsub", "bigquery"] } +google-cloud-pubsub = "0.25" http = "0.2" +iceberg = { workspace = true } +iceberg-catalog-rest = { workspace = true } icelake = { workspace = true } -indexmap = { version = "1.9.3", features = ["serde"] } +indexmap = { version = "2.2.6", features = ["serde"] } itertools = { workspace = true } jni = { version = "0.21.1", features = ["invocation"] } +jsonbb = { workspace = true } jsonwebtoken = "9.2.0" -jst = { package = 'jsonschema-transpiler', git = "https://github.com/mozilla/jsonschema-transpiler", rev = "c1a89d720d118843d8bcca51084deb0ed223e4b4" } maplit = "1.0.2" moka = { version = "0.12", features = ["future"] } +mongodb = { version = "2.8.2", features = ["tokio-runtime"] } mysql_async = { version = "0.34", default-features = false, features = [ "default", ] } @@ -76,10 +84,16 @@ mysql_common = { version = "0.32", default-features = false, features = [ ] } nexmark = { version = "0.2", features = ["serde"] } num-bigint = "0.4" -opendal = "0.45" +opendal = { version = "0.47", features = [ + "executors-tokio", + "services-fs", + "services-gcs", + "services-memory", + "services-s3", +] } openssl = "0.10" parking_lot = { workspace = true } -parquet = "50" +parquet = { workspace = true, features = ["async"] } paste = "1" pg_bigdecimal = { git = "https://github.com/risingwavelabs/rust-pg_bigdecimal", rev = "0b7893d88894ca082b4525f94f812da034486f7c" } postgres-openssl = "0.5.0" @@ -87,8 +101,8 @@ prometheus = { version = "0.13", features = ["process"] } prost = { version = "0.12", features = ["no-recursion-limit"] } prost-reflect = "0.13" prost-types = "0.12" -protobuf-native = "0.2.1" -pulsar = { version = "6.1", default-features = false, features = [ +protobuf-native = "0.2.2" +pulsar = { version = "6.3", default-features = false, features = [ "tokio-runtime", "telemetry", "auth-oauth2", @@ -97,10 +111,7 @@ pulsar = { version = "6.1", default-features = false, features = [ ] } rdkafka = { workspace = true, features = [ "cmake-build", - # "ssl", - # FIXME: temporary workaround before we find an ideal solution. - # See why it's needed and why it's not ideal in https://github.com/risingwavelabs/risingwave/issues/9852 - "ssl-vendored", + "ssl", "gssapi", "zstd", ] } @@ -109,6 +120,7 @@ regex = "1.4" reqwest = { version = "0.12.2", features = ["json", "stream"] } risingwave_common = { workspace = true } risingwave_common_estimate_size = { workspace = true } +risingwave_connector_codec = { workspace = true } risingwave_jni_core = { workspace = true } risingwave_object_store = { workspace = true } risingwave_pb = { workspace = true } @@ -119,16 +131,23 @@ rustls-native-certs = "0.7" rustls-pemfile = "2" rustls-pki-types = "1" rw_futures_util = { workspace = true } +sea-schema = { version = "0.14", default-features = false, features = [ + "discovery", + "sqlx-postgres", + "sqlx-mysql", +] } serde = { version = "1", features = ["derive", "rc"] } serde_derive = "1" serde_json = "1" serde_with = { version = "3", features = ["json"] } -simd-json = "0.13.3" +simd-json = { version = "0.13.3", features = ["hints"] } +sqlx = { workspace = true } strum = "0.26" strum_macros = "0.26" tempfile = "3" thiserror = "1" thiserror-ext = { workspace = true } +tiberius = { version = "0.12", default-features = false, features = ["chrono", "time", "tds73", "rust_decimal", "bigdecimal", "rustls"] } time = "0.3.30" tokio = { version = "0.2", package = "madsim-tokio", features = [ "rt", @@ -142,9 +161,10 @@ tokio = { version = "0.2", package = "madsim-tokio", features = [ tokio-postgres = { version = "0.7", features = ["with-uuid-1"] } tokio-retry = "0.3" tokio-stream = "0.1" -tokio-util = { version = "0.7", features = ["codec", "io"] } +tokio-util = { workspace = true, features = ["codec", "io"] } tonic = { workspace = true } tracing = "0.1" +typed-builder = "^0.18" url = "2" urlencoding = "2" uuid = { version = "1", features = ["v4", "fast-rng"] } @@ -176,7 +196,7 @@ prost-build = "0.12" protobuf-src = "1" [[bench]] -name = "parser" +name = "debezium_json_parser" harness = false [[bench]] @@ -184,7 +204,11 @@ name = "nexmark_integration" harness = false [[bench]] -name = "json_parser" +name = "json_parser_case_insensitive" +harness = false + +[[bench]] +name = "json_vs_plain_parser" harness = false [lints] diff --git a/src/connector/benches/json_parser.rs b/src/connector/benches/debezium_json_parser.rs similarity index 51% rename from src/connector/benches/json_parser.rs rename to src/connector/benches/debezium_json_parser.rs index 90c78044d6221..e448fa17ad1db 100644 --- a/src/connector/benches/json_parser.rs +++ b/src/connector/benches/debezium_json_parser.rs @@ -12,20 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; +//! Benchmark for Debezium JSON records with `DebeziumParser`. + +mod json_common; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use futures::executor::block_on; +use json_common::*; use paste::paste; -use rand::distributions::Alphanumeric; -use rand::prelude::*; -use risingwave_common::catalog::ColumnId; -use risingwave_common::types::{DataType, Date, Timestamp}; -use risingwave_connector::parser::plain_parser::PlainParser; -use risingwave_connector::parser::{ - DebeziumParser, SourceStreamChunkBuilder, SpecificParserConfig, -}; -use risingwave_connector::source::{SourceColumnDesc, SourceContext}; +use rand::Rng; +use risingwave_connector::parser::{DebeziumParser, SourceStreamChunkBuilder}; + +fn generate_debezium_json_row(rng: &mut impl Rng, change_event: &str) -> String { + let source = r#"{"version":"1.7.1.Final","connector":"mysql","name":"dbserver1","ts_ms":1639547113601,"snapshot":"true","db":"inventory","sequence":null,"table":"products","server_id":0,"gtid":null,"file":"mysql-bin.000003","pos":156,"row":0,"thread":null,"query":null}"#; + let (before, after) = match change_event { + "c" => ("null".to_string(), generate_json_row(rng)), + "r" => ("null".to_string(), generate_json_row(rng)), + "u" => (generate_json_row(rng), generate_json_row(rng)), + "d" => (generate_json_row(rng), "null".to_string()), + _ => unreachable!(), + }; + format!("{{\"before\": {before}, \"after\": {after}, \"source\": {source}, \"op\": \"{change_event}\", \"ts_ms\":1639551564960, \"transaction\":null}}") +} macro_rules! create_debezium_bench_helpers { ($op:ident, $op_sym:expr, $bench_function:expr) => { @@ -68,100 +76,8 @@ create_debezium_bench_helpers!(read, "r", "bench_debezium_json_parser_read"); create_debezium_bench_helpers!(update, "u", "bench_debezium_json_parser_update"); create_debezium_bench_helpers!(delete, "d", "bench_debezium_json_parser_delete"); -const NUM_RECORDS: usize = 1 << 18; // ~ 250,000 - -fn generate_json_row(rng: &mut impl Rng) -> String { - format!("{{\"i32\":{},\"bool\":{},\"i16\":{},\"i64\":{},\"f32\":{},\"f64\":{},\"varchar\":\"{}\",\"date\":\"{}\",\"timestamp\":\"{}\"}}", - rng.gen::(), - rng.gen::(), - rng.gen::(), - rng.gen::(), - rng.gen::(), - rng.gen::(), - rng.sample_iter(&Alphanumeric) - .take(7) - .map(char::from) - .collect::(), - Date::from_num_days_from_ce_uncheck((rng.gen::() % (1 << 20)) as i32).0, - { - let datetime = Timestamp::from_timestamp_uncheck((rng.gen::() % (1u32 << 28)) as i64, 0).0; - format!("{:?} {:?}", datetime.date(), datetime.time()) - } - ) -} - -fn generate_json_rows() -> Vec> { - let mut rng = rand::thread_rng(); - let mut records = Vec::with_capacity(NUM_RECORDS); - for _ in 0..NUM_RECORDS { - records.push(generate_json_row(&mut rng).into_bytes()); - } - records -} - -fn generate_debezium_json_row(rng: &mut impl Rng, change_event: &str) -> String { - let source = r#"{"version":"1.7.1.Final","connector":"mysql","name":"dbserver1","ts_ms":1639547113601,"snapshot":"true","db":"inventory","sequence":null,"table":"products","server_id":0,"gtid":null,"file":"mysql-bin.000003","pos":156,"row":0,"thread":null,"query":null}"#; - let (before, after) = match change_event { - "c" => ("null".to_string(), generate_json_row(rng)), - "r" => ("null".to_string(), generate_json_row(rng)), - "u" => (generate_json_row(rng), generate_json_row(rng)), - "d" => (generate_json_row(rng), "null".to_string()), - _ => unreachable!(), - }; - format!("{{\"before\": {before}, \"after\": {after}, \"source\": {source}, \"op\": \"{change_event}\", \"ts_ms\":1639551564960, \"transaction\":null}}") -} - -fn get_descs() -> Vec { - vec![ - SourceColumnDesc::simple("i32", DataType::Int32, ColumnId::from(0)), - SourceColumnDesc::simple("bool", DataType::Boolean, ColumnId::from(2)), - SourceColumnDesc::simple("i16", DataType::Int16, ColumnId::from(3)), - SourceColumnDesc::simple("i64", DataType::Int64, ColumnId::from(4)), - SourceColumnDesc::simple("f32", DataType::Float32, ColumnId::from(5)), - SourceColumnDesc::simple("f64", DataType::Float64, ColumnId::from(6)), - SourceColumnDesc::simple("varchar", DataType::Varchar, ColumnId::from(7)), - SourceColumnDesc::simple("date", DataType::Date, ColumnId::from(8)), - SourceColumnDesc::simple("timestamp", DataType::Timestamp, ColumnId::from(9)), - ] -} - -fn bench_json_parser(c: &mut Criterion) { - let descs = get_descs(); - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - let records = generate_json_rows(); - let ctx = Arc::new(SourceContext::dummy()); - c.bench_function("json_parser", |b| { - b.to_async(&rt).iter_batched( - || records.clone(), - |records| async { - let mut parser = rt - .block_on(PlainParser::new( - SpecificParserConfig::DEFAULT_PLAIN_JSON, - descs.clone(), - ctx.clone(), - )) - .unwrap(); - let mut builder = - SourceStreamChunkBuilder::with_capacity(descs.clone(), NUM_RECORDS); - for record in records { - let writer = builder.row_writer(); - parser - .parse_inner(None, Some(record), writer) - .await - .unwrap(); - } - }, - BatchSize::SmallInput, - ) - }); -} - criterion_group!( benches, - bench_json_parser, bench_debezium_json_parser_create, bench_debezium_json_parser_read, bench_debezium_json_parser_update, diff --git a/src/connector/benches/json_common/mod.rs b/src/connector/benches/json_common/mod.rs new file mode 100644 index 0000000000000..cb67c4cb3d547 --- /dev/null +++ b/src/connector/benches/json_common/mod.rs @@ -0,0 +1,57 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +//! Common utilities shared by JSON parser benchmarks. + +use rand::distributions::Alphanumeric; +use rand::prelude::*; +use risingwave_common::catalog::ColumnId; +use risingwave_common::types::{DataType, Date, Timestamp}; +use risingwave_connector::source::SourceColumnDesc; + +pub const NUM_RECORDS: usize = 1 << 18; // ~ 250,000 + +pub fn generate_json_row(rng: &mut impl Rng) -> String { + format!("{{\"i32\":{},\"bool\":{},\"i16\":{},\"i64\":{},\"f32\":{},\"f64\":{},\"varchar\":\"{}\",\"date\":\"{}\",\"timestamp\":\"{}\"}}", + rng.gen::(), + rng.gen::(), + rng.gen::(), + rng.gen::(), + rng.gen::(), + rng.gen::(), + rng.sample_iter(&Alphanumeric) + .take(7) + .map(char::from) + .collect::(), + Date::from_num_days_from_ce_uncheck((rng.gen::() % (1 << 20)) as i32).0, + { + let datetime = Timestamp::from_timestamp_uncheck((rng.gen::() % (1u32 << 28)) as i64, 0).0; + format!("{:?} {:?}", datetime.date(), datetime.time()) + } + ) +} + +pub fn get_descs() -> Vec { + vec![ + SourceColumnDesc::simple("i32", DataType::Int32, ColumnId::from(0)), + SourceColumnDesc::simple("bool", DataType::Boolean, ColumnId::from(2)), + SourceColumnDesc::simple("i16", DataType::Int16, ColumnId::from(3)), + SourceColumnDesc::simple("i64", DataType::Int64, ColumnId::from(4)), + SourceColumnDesc::simple("f32", DataType::Float32, ColumnId::from(5)), + SourceColumnDesc::simple("f64", DataType::Float64, ColumnId::from(6)), + SourceColumnDesc::simple("varchar", DataType::Varchar, ColumnId::from(7)), + SourceColumnDesc::simple("date", DataType::Date, ColumnId::from(8)), + SourceColumnDesc::simple("timestamp", DataType::Timestamp, ColumnId::from(9)), + ] +} diff --git a/src/connector/benches/parser.rs b/src/connector/benches/json_parser_case_insensitive.rs similarity index 73% rename from src/connector/benches/parser.rs rename to src/connector/benches/json_parser_case_insensitive.rs index 21ce72dd1b2b1..17fd439e6ccc1 100644 --- a/src/connector/benches/parser.rs +++ b/src/connector/benches/json_parser_case_insensitive.rs @@ -12,24 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Benchmarking JSON parsers for scenarios with exact key matches and case-insensitive key matches. + use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; +use futures::StreamExt; use maplit::hashmap; use rand::Rng; use risingwave_common::types::DataType; use risingwave_connector::parser::{ - EncodingProperties, JsonParser, JsonProperties, ProtocolProperties, SourceStreamChunkBuilder, - SpecificParserConfig, + ByteStreamSourceParserImpl, CommonParserConfig, ParserConfig, SpecificParserConfig, }; -use risingwave_connector::source::{SourceColumnDesc, SourceContext}; +use risingwave_connector::source::{SourceColumnDesc, SourceMessage}; use serde_json::json; use tokio::runtime::Runtime; -fn gen_input(mode: &str, chunk_size: usize, chunk_num: usize) -> Vec>> { +type Input = Vec>; +type Parser = ByteStreamSourceParserImpl; + +fn gen_input(mode: &str, chunk_size: usize, chunk_num: usize) -> Input { let mut input = Vec::with_capacity(chunk_num); for _ in 0..chunk_num { let mut input_inner = Vec::with_capacity(chunk_size); for _ in 0..chunk_size { - input_inner.push(match mode { + let payload = match mode { "match" => r#"{"alpha": 1, "bravo": 2, "charlie": 3, "delta": 4}"# .as_bytes() .to_vec(), @@ -55,6 +60,10 @@ fn gen_input(mode: &str, chunk_size: usize, chunk_num: usize) -> Vec serde_json::to_string(&value).unwrap().as_bytes().to_vec() } _ => unreachable!(), + }; + input_inner.push(SourceMessage { + payload: Some(payload), + ..SourceMessage::dummy() }); } input.push(input_inner); @@ -62,40 +71,27 @@ fn gen_input(mode: &str, chunk_size: usize, chunk_num: usize) -> Vec input } -fn create_parser( - chunk_size: usize, - chunk_num: usize, - mode: &str, -) -> (JsonParser, Vec, Vec>>) { +fn create_parser(chunk_size: usize, chunk_num: usize, mode: &str) -> (Parser, Input) { let desc = vec![ SourceColumnDesc::simple("alpha", DataType::Int16, 0.into()), SourceColumnDesc::simple("bravo", DataType::Int32, 1.into()), SourceColumnDesc::simple("charlie", DataType::Int64, 2.into()), SourceColumnDesc::simple("delta", DataType::Int64, 3.into()), ]; - let props = SpecificParserConfig { - key_encoding_config: None, - encoding_config: EncodingProperties::Json(JsonProperties { - use_schema_registry: false, - timestamptz_handling: None, - }), - protocol_config: ProtocolProperties::Plain, + let config = ParserConfig { + common: CommonParserConfig { rw_columns: desc }, + specific: SpecificParserConfig::DEFAULT_PLAIN_JSON, }; - let parser = JsonParser::new(props, desc.clone(), SourceContext::dummy().into()).unwrap(); + let parser = ByteStreamSourceParserImpl::create_for_test(config).unwrap(); let input = gen_input(mode, chunk_size, chunk_num); - (parser, desc, input) + (parser, input) } -async fn parse(parser: JsonParser, column_desc: Vec, input: Vec>>) { - for input_inner in input { - let mut builder = - SourceStreamChunkBuilder::with_capacity(column_desc.clone(), input_inner.len()); - for payload in input_inner { - let row_writer = builder.row_writer(); - parser.parse_inner(payload, row_writer).await.unwrap(); - } - builder.finish(); - } +async fn parse(parser: Parser, input: Input) { + parser + .into_stream(futures::stream::iter(input.into_iter().map(Ok)).boxed()) + .count() // consume the stream + .await; } fn do_bench(c: &mut Criterion, mode: &str) { @@ -110,7 +106,7 @@ fn do_bench(c: &mut Criterion, mode: &str) { let chunk_num = TOTAL_SIZE / chunk_size; b.to_async(&rt).iter_batched( || create_parser(chunk_size, chunk_num, mode), - |(parser, column_desc, input)| parse(parser, column_desc, input), + |(parser, input)| parse(parser, input), BatchSize::SmallInput, ); }, diff --git a/src/connector/benches/json_vs_plain_parser.rs b/src/connector/benches/json_vs_plain_parser.rs new file mode 100644 index 0000000000000..b9a1139dcb03b --- /dev/null +++ b/src/connector/benches/json_vs_plain_parser.rs @@ -0,0 +1,172 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +//! Benchmark for comparing the performance of parsing JSON records directly +//! through the `JsonParser` versus indirectly through the `PlainParser`. + +mod json_common; + +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use futures::executor::block_on; +use json_common::*; +use old_json_parser::JsonParser; +use risingwave_connector::parser::plain_parser::PlainParser; +use risingwave_connector::parser::{SourceStreamChunkBuilder, SpecificParserConfig}; +use risingwave_connector::source::SourceContext; + +// The original implementation used to parse JSON prior to #13707. +mod old_json_parser { + use anyhow::Context as _; + use itertools::{Either, Itertools as _}; + use risingwave_common::{bail, try_match_expand}; + use risingwave_connector::error::ConnectorResult; + use risingwave_connector::parser::{ + Access as _, EncodingProperties, JsonAccess, SourceStreamChunkRowWriter, + }; + use risingwave_connector::source::{SourceColumnDesc, SourceContextRef}; + + use super::*; + + /// Parser for JSON format + #[derive(Debug)] + pub struct JsonParser { + _rw_columns: Vec, + _source_ctx: SourceContextRef, + // If schema registry is used, the starting index of payload is 5. + payload_start_idx: usize, + } + + impl JsonParser { + pub fn new( + props: SpecificParserConfig, + rw_columns: Vec, + source_ctx: SourceContextRef, + ) -> ConnectorResult { + let json_config = try_match_expand!(props.encoding_config, EncodingProperties::Json)?; + let payload_start_idx = if json_config.use_schema_registry { + 5 + } else { + 0 + }; + Ok(Self { + _rw_columns: rw_columns, + _source_ctx: source_ctx, + payload_start_idx, + }) + } + + #[allow(clippy::unused_async)] + pub async fn parse_inner( + &self, + mut payload: Vec, + mut writer: SourceStreamChunkRowWriter<'_>, + ) -> ConnectorResult<()> { + let value = simd_json::to_borrowed_value(&mut payload[self.payload_start_idx..]) + .context("failed to parse json payload")?; + let values = if let simd_json::BorrowedValue::Array(arr) = value { + Either::Left(arr.into_iter()) + } else { + Either::Right(std::iter::once(value)) + }; + + let mut errors = Vec::new(); + for value in values { + let accessor = JsonAccess::new(value); + match writer.do_insert(|column| accessor.access(&[&column.name], &column.data_type)) + { + Ok(_) => {} + Err(err) => errors.push(err), + } + } + + if errors.is_empty() { + Ok(()) + } else { + bail!( + "failed to parse {} row(s) in a single json message: {}", + errors.len(), + errors.iter().format(", ") + ); + } + } + } +} + +fn generate_json_rows() -> Vec> { + let mut rng = rand::thread_rng(); + let mut records = Vec::with_capacity(NUM_RECORDS); + for _ in 0..NUM_RECORDS { + records.push(generate_json_row(&mut rng).into_bytes()); + } + records +} + +fn bench_plain_parser_and_json_parser(c: &mut Criterion) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let records = generate_json_rows(); + + let mut group = c.benchmark_group("plain parser and json parser comparison"); + + group.bench_function("plain_parser", |b| { + b.to_async(&rt).iter_batched( + || { + let parser = block_on(PlainParser::new( + SpecificParserConfig::DEFAULT_PLAIN_JSON, + get_descs(), + SourceContext::dummy().into(), + )) + .unwrap(); + (parser, records.clone()) + }, + |(mut parser, records)| async move { + let mut builder = SourceStreamChunkBuilder::with_capacity(get_descs(), NUM_RECORDS); + for record in records { + let writer = builder.row_writer(); + parser + .parse_inner(None, Some(record), writer) + .await + .unwrap(); + } + }, + BatchSize::SmallInput, + ) + }); + + group.bench_function("json_parser", |b| { + b.to_async(&rt).iter_batched( + || { + let parser = JsonParser::new( + SpecificParserConfig::DEFAULT_PLAIN_JSON, + get_descs(), + SourceContext::dummy().into(), + ) + .unwrap(); + (parser, records.clone()) + }, + |(parser, records)| async move { + let mut builder = SourceStreamChunkBuilder::with_capacity(get_descs(), NUM_RECORDS); + for record in records { + let writer = builder.row_writer(); + parser.parse_inner(record, writer).await.unwrap(); + } + }, + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +criterion_group!(benches, bench_plain_parser_and_json_parser,); +criterion_main!(benches); diff --git a/src/connector/benches/nexmark_integration.rs b/src/connector/benches/nexmark_integration.rs index 1c05147eeafbb..172931562efef 100644 --- a/src/connector/benches/nexmark_integration.rs +++ b/src/connector/benches/nexmark_integration.rs @@ -12,6 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Integration benchmark for parsing Nexmark events. +//! +//! To cover the code path in real-world scenarios, the parser is created through +//! `ByteStreamSourceParserImpl::create` based on the given configuration, rather +//! than depending on a specific internal implementation. + #![feature(lazy_cell)] use std::sync::LazyLock; @@ -20,27 +26,23 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use futures::{FutureExt, StreamExt, TryStreamExt}; use itertools::Itertools; use risingwave_common::array::StreamChunk; -use risingwave_common::catalog::ColumnId; +use risingwave_common::catalog::{ColumnDesc, ColumnId}; use risingwave_common::types::DataType; use risingwave_connector::parser::{ - ByteStreamSourceParser, JsonParser, SourceParserIntoStreamExt, SpecificParserConfig, + ByteStreamSourceParserImpl, CommonParserConfig, ParserConfig, SpecificParserConfig, }; use risingwave_connector::source::{ - BoxChunkSourceStream, BoxSourceStream, SourceColumnDesc, SourceContext, SourceMessage, - SourceMeta, + BoxChunkSourceStream, BoxSourceStream, SourceColumnDesc, SourceMessage, SourceMeta, }; use tracing::Level; use tracing_subscriber::prelude::*; -static BATCH: LazyLock> = LazyLock::new(make_batch); +static BATCH: LazyLock> = LazyLock::new(|| make_batch(false)); +static STRUCT_BATCH: LazyLock> = LazyLock::new(|| make_batch(true)); -fn make_batch() -> Vec { +fn make_batch(use_struct: bool) -> Vec { let mut generator = nexmark::EventGenerator::default() .with_type_filter(nexmark::event::EventType::Bid) - .map(|e| match e { - nexmark::event::Event::Bid(bid) => bid, // extract the bid event - _ => unreachable!(), - }) .enumerate(); let message_base = SourceMessage { @@ -53,9 +55,16 @@ fn make_batch() -> Vec { generator .by_ref() - .take(1024) - .map(|(i, e)| { - let payload = serde_json::to_vec(&e).unwrap(); + .take(16384) + .map(|(i, event)| { + let payload = if use_struct { + serde_json::to_vec(&event).unwrap() + } else { + let nexmark::event::Event::Bid(bid) = event else { + unreachable!() + }; + serde_json::to_vec(&bid).unwrap() + }; SourceMessage { payload: Some(payload), offset: i.to_string(), @@ -65,14 +74,18 @@ fn make_batch() -> Vec { .collect_vec() } -fn make_data_stream() -> BoxSourceStream { - futures::future::ready(Ok(BATCH.clone())) - .into_stream() - .boxed() +fn make_data_stream(use_struct: bool) -> BoxSourceStream { + futures::future::ready(Ok(if use_struct { + STRUCT_BATCH.clone() + } else { + BATCH.clone() + })) + .into_stream() + .boxed() } -fn make_parser() -> impl ByteStreamSourceParser { - let columns = [ +fn make_parser(use_struct: bool) -> ByteStreamSourceParserImpl { + let fields = vec![ ("auction", DataType::Int64), ("bidder", DataType::Int64), ("price", DataType::Int64), @@ -80,19 +93,36 @@ fn make_parser() -> impl ByteStreamSourceParser { ("url", DataType::Varchar), ("date_time", DataType::Timestamp), ("extra", DataType::Varchar), - ] - .into_iter() - .enumerate() - .map(|(i, (n, t))| SourceColumnDesc::simple(n, t, ColumnId::new(i as _))) - .collect_vec(); + ]; + + let rw_columns = if use_struct { + let fields = fields + .into_iter() + .enumerate() + .map(|(i, (n, t))| ColumnDesc::named(n, ColumnId::new(i as _), t)) + .collect(); + let struct_col = ColumnDesc::new_struct("bid", 114514, "bid", fields); + vec![(&struct_col).into()] + } else { + fields + .into_iter() + .enumerate() + .map(|(i, (n, t))| SourceColumnDesc::simple(n, t, ColumnId::new(i as _))) + .collect_vec() + }; - let props = SpecificParserConfig::DEFAULT_PLAIN_JSON; + let config = ParserConfig { + common: CommonParserConfig { rw_columns }, + specific: SpecificParserConfig::DEFAULT_PLAIN_JSON, + }; - JsonParser::new(props, columns, SourceContext::dummy().into()).unwrap() + ByteStreamSourceParserImpl::create_for_test(config).unwrap() } -fn make_stream_iter() -> impl Iterator { - let mut stream: BoxChunkSourceStream = make_parser().into_stream(make_data_stream()).boxed(); +fn make_stream_iter(use_struct: bool) -> impl Iterator { + let mut stream: BoxChunkSourceStream = make_parser(use_struct) + .into_stream(make_data_stream(use_struct)) + .boxed(); std::iter::from_fn(move || { stream @@ -108,7 +138,7 @@ fn make_stream_iter() -> impl Iterator { fn bench(c: &mut Criterion) { c.bench_function("parse_nexmark", |b| { b.iter_batched( - make_stream_iter, + || make_stream_iter(false), |mut iter| iter.next().unwrap(), BatchSize::SmallInput, ) @@ -127,11 +157,19 @@ fn bench(c: &mut Criterion) { .into(); b.iter_batched( - make_stream_iter, + || make_stream_iter(false), |mut iter| tracing::dispatcher::with_default(&dispatch, || iter.next().unwrap()), BatchSize::SmallInput, ) }); + + c.bench_function("parse_nexmark_struct_type", |b| { + b.iter_batched( + || make_stream_iter(true), + |mut iter| iter.next().unwrap(), + BatchSize::SmallInput, + ) + }); } criterion_group!(benches, bench); diff --git a/src/connector/codec/Cargo.toml b/src/connector/codec/Cargo.toml new file mode 100644 index 0000000000000..5086549f4bf4c --- /dev/null +++ b/src/connector/codec/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "risingwave_connector_codec" +description = "Encoding and decoding between external data formats and RisingWave datum" +version = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +keywords = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[package.metadata.cargo-machete] +ignored = ["workspace-hack"] + +[package.metadata.cargo-udeps.ignore] +normal = ["workspace-hack"] + +[dependencies] +anyhow = "1" +apache-avro = { workspace = true } +chrono = { version = "0.4", default-features = false, features = [ + "clock", + "std", +] } +easy-ext = "1" +itertools = { workspace = true } +jsonbb = { workspace = true } +jst = { package = 'jsonschema-transpiler', git = "https://github.com/mozilla/jsonschema-transpiler", rev = "c1a89d720d118843d8bcca51084deb0ed223e4b4" } +num-bigint = "0.4" +risingwave_common = { workspace = true } +risingwave_pb = { workspace = true } +rust_decimal = "1" +serde_json = "1.0" +thiserror = "1" +thiserror-ext = { workspace = true } +time = "0.3.30" +tracing = "0.1" + +[dev-dependencies] +expect-test = "1" +hex = "0.4" + +[target.'cfg(not(madsim))'.dependencies] +workspace-hack = { path = "../../workspace-hack" } + +[lints] +workspace = true diff --git a/src/connector/src/parser/unified/avro.rs b/src/connector/codec/src/decoder/avro/mod.rs similarity index 60% rename from src/connector/src/parser/unified/avro.rs rename to src/connector/codec/src/decoder/avro/mod.rs index 4e7d134f42554..cdd9aea416c8f 100644 --- a/src/connector/src/parser/unified/avro.rs +++ b/src/connector/codec/src/decoder/avro/mod.rs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::str::FromStr; +mod schema; use std::sync::LazyLock; use apache_avro::schema::{DecimalSchema, RecordSchema}; -use apache_avro::types::Value; +use apache_avro::types::{Value, ValueKind}; use apache_avro::{Decimal as AvroDecimal, Schema}; use chrono::Datelike; use itertools::Itertools; @@ -25,37 +25,39 @@ use risingwave_common::array::{ListValue, StructValue}; use risingwave_common::bail; use risingwave_common::log::LogSuppresser; use risingwave_common::types::{ - DataType, Date, Datum, Interval, JsonbVal, ScalarImpl, Time, Timestamp, Timestamptz, + DataType, Date, DatumCow, Interval, JsonbVal, ScalarImpl, Time, Timestamp, Timestamptz, + ToOwnedDatum, }; use risingwave_common::util::iter_util::ZipEqFast; +pub use self::schema::{avro_schema_to_column_descs, MapHandling, ResolvedAvroSchema}; +use super::utils::extract_decimal; use super::{bail_uncategorized, uncategorized, Access, AccessError, AccessResult}; -use crate::error::ConnectorResult; + #[derive(Clone)] /// Options for parsing an `AvroValue` into Datum, with an optional avro schema. pub struct AvroParseOptions<'a> { + /// Currently, this schema is only used for decimal. + /// + /// FIXME: In theory we should use resolved schema. + /// e.g., it's possible that a field is a reference to a decimal or a record containing a decimal field. pub schema: Option<&'a Schema>, /// Strict Mode /// If strict mode is disabled, an int64 can be parsed from an `AvroInt` (int32) value. pub relax_numeric: bool, } -impl<'a> Default for AvroParseOptions<'a> { - fn default() -> Self { +impl<'a> AvroParseOptions<'a> { + pub fn create(schema: &'a Schema) -> Self { Self { - schema: None, + schema: Some(schema), relax_numeric: true, } } } impl<'a> AvroParseOptions<'a> { - pub fn with_schema(mut self, schema: &'a Schema) -> Self { - self.schema = Some(schema); - self - } - - fn extract_inner_schema(&self, key: Option<&'a str>) -> Option<&'a Schema> { + fn extract_inner_schema(&self, key: Option<&str>) -> Option<&'a Schema> { self.schema .map(|schema| avro_extract_field_schema(schema, key)) .transpose() @@ -71,15 +73,23 @@ impl<'a> AvroParseOptions<'a> { } /// Parse an avro value into expected type. - /// 3 kinds of type info are used to parsing things. - /// - `type_expected`. The type that we expect the value is. - /// - value type. The type info together with the value argument. - /// - schema. The `AvroSchema` provided in option. - /// If both `type_expected` and schema are provided, it will check both strictly. - /// If only `type_expected` is provided, it will try to match the value type and the - /// `type_expected`, converting the value if possible. If only value is provided (without - /// schema and `type_expected`), the `DateType` will be inferred. - pub fn parse<'b>(&self, value: &'b Value, type_expected: Option<&'b DataType>) -> AccessResult + /// + /// 3 kinds of type info are used to parsing: + /// - `type_expected`. The type that we expect the value is. + /// - value type. The type info together with the value argument. + /// - schema. The `AvroSchema` provided in option. + /// + /// Cases: (FIXME: Is this precise?) + /// - If both `type_expected` and schema are provided, it will check both strictly. + /// - If only `type_expected` is provided, it will try to match the value type and the + /// `type_expected`, converting the value if possible. + /// - If only value is provided (without schema and `type_expected`), + /// the `DataType` will be inferred. + pub fn convert_to_datum<'b>( + &self, + value: &'b Value, + type_expected: &DataType, + ) -> AccessResult> where 'b: 'a, { @@ -89,36 +99,42 @@ impl<'a> AvroParseOptions<'a> { value: String::new(), }; + macro_rules! borrowed { + ($v:expr) => { + return Ok(DatumCow::Borrowed(Some($v.into()))) + }; + } + let v: ScalarImpl = match (type_expected, value) { - (_, Value::Null) => return Ok(None), + (_, Value::Null) => return Ok(DatumCow::NULL), (_, Value::Union(_, v)) => { let schema = self.extract_inner_schema(None); return Self { schema, relax_numeric: self.relax_numeric, } - .parse(v, type_expected); + .convert_to_datum(v, type_expected); } // ---- Boolean ----- - (Some(DataType::Boolean) | None, Value::Boolean(b)) => (*b).into(), + (DataType::Boolean, Value::Boolean(b)) => (*b).into(), // ---- Int16 ----- - (Some(DataType::Int16), Value::Int(i)) if self.relax_numeric => (*i as i16).into(), - (Some(DataType::Int16), Value::Long(i)) if self.relax_numeric => (*i as i16).into(), + (DataType::Int16, Value::Int(i)) if self.relax_numeric => (*i as i16).into(), + (DataType::Int16, Value::Long(i)) if self.relax_numeric => (*i as i16).into(), // ---- Int32 ----- - (Some(DataType::Int32) | None, Value::Int(i)) => (*i).into(), - (Some(DataType::Int32), Value::Long(i)) if self.relax_numeric => (*i as i32).into(), + (DataType::Int32, Value::Int(i)) => (*i).into(), + (DataType::Int32, Value::Long(i)) if self.relax_numeric => (*i as i32).into(), // ---- Int64 ----- - (Some(DataType::Int64) | None, Value::Long(i)) => (*i).into(), - (Some(DataType::Int64), Value::Int(i)) if self.relax_numeric => (*i as i64).into(), + (DataType::Int64, Value::Long(i)) => (*i).into(), + (DataType::Int64, Value::Int(i)) if self.relax_numeric => (*i as i64).into(), // ---- Float32 ----- - (Some(DataType::Float32) | None, Value::Float(i)) => (*i).into(), - (Some(DataType::Float32), Value::Double(i)) => (*i as f32).into(), + (DataType::Float32, Value::Float(i)) => (*i).into(), + (DataType::Float32, Value::Double(i)) => (*i as f32).into(), // ---- Float64 ----- - (Some(DataType::Float64) | None, Value::Double(i)) => (*i).into(), - (Some(DataType::Float64), Value::Float(i)) => (*i as f64).into(), + (DataType::Float64, Value::Double(i)) => (*i).into(), + (DataType::Float64, Value::Float(i)) => (*i as f64).into(), // ---- Decimal ----- - (Some(DataType::Decimal) | None, Value::Decimal(avro_decimal)) => { + (DataType::Decimal, Value::Decimal(avro_decimal)) => { let (precision, scale) = match self.schema { Some(Schema::Decimal(DecimalSchema { precision, scale, .. @@ -129,7 +145,7 @@ impl<'a> AvroParseOptions<'a> { .map_err(|_| create_error())?; ScalarImpl::Decimal(risingwave_common::types::Decimal::Normalized(decimal)) } - (Some(DataType::Decimal), Value::Record(fields)) => { + (DataType::Decimal, Value::Record(fields)) => { // VariableScaleDecimal has fixed fields, scale(int) and value(bytes) let find_in_records = |field_name: &str| { fields @@ -163,56 +179,46 @@ impl<'a> AvroParseOptions<'a> { ScalarImpl::Decimal(risingwave_common::types::Decimal::Normalized(decimal)) } // ---- Time ----- - (Some(DataType::Time), Value::TimeMillis(ms)) => Time::with_milli(*ms as u32) + (DataType::Time, Value::TimeMillis(ms)) => Time::with_milli(*ms as u32) .map_err(|_| create_error())? .into(), - (Some(DataType::Time), Value::TimeMicros(us)) => Time::with_micro(*us as u64) + (DataType::Time, Value::TimeMicros(us)) => Time::with_micro(*us as u64) .map_err(|_| create_error())? .into(), // ---- Date ----- - (Some(DataType::Date) | None, Value::Date(days)) => { - Date::with_days(days + unix_epoch_days()) - .map_err(|_| create_error())? - .into() - } + (DataType::Date, Value::Date(days)) => Date::with_days(days + unix_epoch_days()) + .map_err(|_| create_error())? + .into(), // ---- Varchar ----- - (Some(DataType::Varchar) | None, Value::Enum(_, symbol)) => { - symbol.clone().into_boxed_str().into() - } - (Some(DataType::Varchar) | None, Value::String(s)) => s.clone().into_boxed_str().into(), + (DataType::Varchar, Value::Enum(_, symbol)) => borrowed!(symbol.as_str()), + (DataType::Varchar, Value::String(s)) => borrowed!(s.as_str()), // ---- Timestamp ----- - (Some(DataType::Timestamp) | None, Value::LocalTimestampMillis(ms)) => { - Timestamp::with_millis(*ms) - .map_err(|_| create_error())? - .into() - } - (Some(DataType::Timestamp) | None, Value::LocalTimestampMicros(us)) => { - Timestamp::with_micros(*us) - .map_err(|_| create_error())? - .into() - } + (DataType::Timestamp, Value::LocalTimestampMillis(ms)) => Timestamp::with_millis(*ms) + .map_err(|_| create_error())? + .into(), + (DataType::Timestamp, Value::LocalTimestampMicros(us)) => Timestamp::with_micros(*us) + .map_err(|_| create_error())? + .into(), // ---- TimestampTz ----- - (Some(DataType::Timestamptz) | None, Value::TimestampMillis(ms)) => { - Timestamptz::from_millis(*ms) - .ok_or_else(|| { - uncategorized!("timestamptz with milliseconds {ms} * 1000 is out of range") - })? - .into() - } - (Some(DataType::Timestamptz) | None, Value::TimestampMicros(us)) => { + (DataType::Timestamptz, Value::TimestampMillis(ms)) => Timestamptz::from_millis(*ms) + .ok_or_else(|| { + uncategorized!("timestamptz with milliseconds {ms} * 1000 is out of range") + })? + .into(), + (DataType::Timestamptz, Value::TimestampMicros(us)) => { Timestamptz::from_micros(*us).into() } // ---- Interval ----- - (Some(DataType::Interval) | None, Value::Duration(duration)) => { + (DataType::Interval, Value::Duration(duration)) => { let months = u32::from(duration.months()) as i32; let days = u32::from(duration.days()) as i32; let usecs = (u32::from(duration.millis()) as i64) * 1000; // never overflows ScalarImpl::Interval(Interval::from_month_day_usec(months, days, usecs)) } // ---- Struct ----- - (Some(DataType::Struct(struct_type_info)), Value::Record(descs)) => StructValue::new( + (DataType::Struct(struct_type_info), Value::Record(descs)) => StructValue::new( struct_type_info .names() .zip_eq_fast(struct_type_info.types()) @@ -224,7 +230,8 @@ impl<'a> AvroParseOptions<'a> { schema, relax_numeric: self.relax_numeric, } - .parse(value, Some(field_type))?) + .convert_to_datum(value, field_type)? + .to_owned_datum()) } else { Ok(None) } @@ -232,22 +239,8 @@ impl<'a> AvroParseOptions<'a> { .collect::>()?, ) .into(), - (None, Value::Record(descs)) => { - let rw_values = descs - .iter() - .map(|(field_name, field_value)| { - let schema = self.extract_inner_schema(Some(field_name)); - Self { - schema, - relax_numeric: self.relax_numeric, - } - .parse(field_value, None) - }) - .collect::, AccessError>>()?; - ScalarImpl::Struct(StructValue::new(rw_values)) - } // ---- List ----- - (Some(DataType::List(item_type)), Value::Array(array)) => ListValue::new({ + (DataType::List(item_type), Value::Array(array)) => ListValue::new({ let schema = self.extract_inner_schema(None); let mut builder = item_type.create_array_builder(array.len()); for v in array { @@ -255,43 +248,45 @@ impl<'a> AvroParseOptions<'a> { schema, relax_numeric: self.relax_numeric, } - .parse(v, Some(item_type))?; + .convert_to_datum(v, item_type)?; builder.append(value); } builder.finish() }) .into(), // ---- Bytea ----- - (Some(DataType::Bytea) | None, Value::Bytes(value)) => { - value.clone().into_boxed_slice().into() - } + (DataType::Bytea, Value::Bytes(value)) => borrowed!(value.as_slice()), // ---- Jsonb ----- - (Some(DataType::Jsonb), Value::String(s)) => { - JsonbVal::from_str(s).map_err(|_| create_error())?.into() + (DataType::Jsonb, v @ Value::Map(_)) => { + let mut builder = jsonbb::Builder::default(); + avro_to_jsonb(v, &mut builder)?; + let jsonb = builder.finish(); + debug_assert!(jsonb.as_ref().is_object()); + JsonbVal::from(jsonb).into() + } + (DataType::Varchar, Value::Uuid(uuid)) => { + uuid.as_hyphenated().to_string().into_boxed_str().into() } (_expected, _got) => Err(create_error())?, }; - Ok(Some(v)) + Ok(DatumCow::Owned(Some(v))) } } -pub struct AvroAccess<'a, 'b> { +pub struct AvroAccess<'a> { value: &'a Value, - options: AvroParseOptions<'b>, + options: AvroParseOptions<'a>, } -impl<'a, 'b> AvroAccess<'a, 'b> { - pub fn new(value: &'a Value, options: AvroParseOptions<'b>) -> Self { +impl<'a> AvroAccess<'a> { + pub fn new(value: &'a Value, options: AvroParseOptions<'a>) -> Self { Self { value, options } } } -impl<'a, 'b> Access for AvroAccess<'a, 'b> -where - 'a: 'b, -{ - fn access(&self, path: &[&str], type_expected: Option<&DataType>) -> AccessResult { +impl Access for AvroAccess<'_> { + fn access<'a>(&'a self, path: &[&str], type_expected: &DataType) -> AccessResult> { let mut value = self.value; let mut options: AvroParseOptions<'_> = self.options.clone(); @@ -308,12 +303,6 @@ where options.schema = options.extract_inner_schema(None); continue; } - Value::Map(fields) if fields.contains_key(key) => { - value = fields.get(key).unwrap(); - options.schema = None; - i += 1; - continue; - } Value::Record(fields) => { if let Some((_, v)) = fields.iter().find(|(k, _)| k == key) { value = v; @@ -327,7 +316,7 @@ where Err(create_error())?; } - options.parse(value, type_expected) + options.convert_to_datum(value, type_expected) } } @@ -349,44 +338,7 @@ pub(crate) fn avro_decimal_to_rust_decimal( )) } -pub(crate) fn extract_decimal(bytes: Vec) -> AccessResult<(u32, u32, u32)> { - match bytes.len() { - len @ 0..=4 => { - let mut pad = vec![0; 4 - len]; - pad.extend_from_slice(&bytes); - let lo = u32::from_be_bytes(pad.try_into().unwrap()); - Ok((lo, 0, 0)) - } - len @ 5..=8 => { - let zero_len = 8 - len; - let mid_end = 4 - zero_len; - - let mut pad = vec![0; zero_len]; - pad.extend_from_slice(&bytes[..mid_end]); - let mid = u32::from_be_bytes(pad.try_into().unwrap()); - - let lo = u32::from_be_bytes(bytes[mid_end..].to_owned().try_into().unwrap()); - Ok((lo, mid, 0)) - } - len @ 9..=12 => { - let zero_len = 12 - len; - let hi_end = 4 - zero_len; - let mid_end = hi_end + 4; - - let mut pad = vec![0; zero_len]; - pad.extend_from_slice(&bytes[..hi_end]); - let hi = u32::from_be_bytes(pad.try_into().unwrap()); - - let mid = u32::from_be_bytes(bytes[hi_end..mid_end].to_owned().try_into().unwrap()); - - let lo = u32::from_be_bytes(bytes[mid_end..].to_owned().try_into().unwrap()); - Ok((lo, mid, hi)) - } - _ => bail_uncategorized!("invalid decimal bytes length {}", bytes.len()), - } -} - -pub fn avro_schema_skip_union(schema: &Schema) -> ConnectorResult<&Schema> { +pub fn avro_schema_skip_union(schema: &Schema) -> anyhow::Result<&Schema> { match schema { Schema::Union(union_schema) => { let inner_schema = union_schema @@ -401,11 +353,12 @@ pub fn avro_schema_skip_union(schema: &Schema) -> ConnectorResult<&Schema> { other => Ok(other), } } + // extract inner filed/item schema of record/array/union pub fn avro_extract_field_schema<'a>( schema: &'a Schema, - name: Option<&'a str>, -) -> ConnectorResult<&'a Schema> { + name: Option<&str>, +) -> anyhow::Result<&'a Schema> { match schema { Schema::Record(RecordSchema { fields, lookup, .. }) => { let name = @@ -420,7 +373,8 @@ pub fn avro_extract_field_schema<'a>( } Schema::Array(schema) => Ok(schema), Schema::Union(_) => avro_schema_skip_union(schema), - _ => bail!("avro schema is not a record or array"), + Schema::Map(schema) => Ok(schema), + _ => bail!("avro schema does not have inner item, schema: {:?}", schema), } } @@ -428,10 +382,102 @@ pub(crate) fn unix_epoch_days() -> i32 { Date::from_ymd_uncheck(1970, 1, 1).0.num_days_from_ce() } +pub(crate) fn avro_to_jsonb(avro: &Value, builder: &mut jsonbb::Builder) -> AccessResult<()> { + match avro { + Value::Null => builder.add_null(), + Value::Boolean(b) => builder.add_bool(*b), + Value::Int(i) => builder.add_i64(*i as i64), + Value::String(s) => builder.add_string(s), + Value::Map(m) => { + builder.begin_object(); + for (k, v) in m { + builder.add_string(k); + avro_to_jsonb(v, builder)?; + } + builder.end_object() + } + // same representation as map + Value::Record(r) => { + builder.begin_object(); + for (k, v) in r { + builder.add_string(k); + avro_to_jsonb(v, builder)?; + } + builder.end_object() + } + Value::Array(a) => { + builder.begin_array(); + for v in a { + avro_to_jsonb(v, builder)?; + } + builder.end_array() + } + + // TODO: figure out where the following encoding is reasonable before enabling them. + // See discussions: https://github.com/risingwavelabs/risingwave/pull/16948 + + // jsonbb supports int64, but JSON spec does not allow it. How should we handle it? + // BTW, protobuf canonical JSON converts int64 to string. + // Value::Long(l) => builder.add_i64(*l), + // Value::Float(f) => { + // if f.is_nan() || f.is_infinite() { + // // XXX: pad null or return err here? + // builder.add_null() + // } else { + // builder.add_f64(*f as f64) + // } + // } + // Value::Double(f) => { + // if f.is_nan() || f.is_infinite() { + // // XXX: pad null or return err here? + // builder.add_null() + // } else { + // builder.add_f64(*f) + // } + // } + // // XXX: What encoding to use? + // // ToText is \x plus hex string. + // Value::Bytes(b) => builder.add_string(&ToText::to_text(&b.as_slice())), + // Value::Enum(_, symbol) => { + // builder.add_string(&symbol); + // } + // Value::Uuid(id) => builder.add_string(&id.as_hyphenated().to_string()), + // // For Union, one concern is that the avro union is tagged (like rust enum) but json union is untagged (like c union). + // // When the union consists of multiple records, it is possible to distinguish which variant is active in avro, but in json they will all become jsonb objects and indistinguishable. + // Value::Union(_, v) => avro_to_jsonb(v, builder)? + // XXX: pad null or return err here? + v @ (Value::Long(_) + | Value::Float(_) + | Value::Double(_) + | Value::Bytes(_) + | Value::Enum(_, _) + | Value::Fixed(_, _) + | Value::Date(_) + | Value::Decimal(_) + | Value::TimeMillis(_) + | Value::TimeMicros(_) + | Value::TimestampMillis(_) + | Value::TimestampMicros(_) + | Value::LocalTimestampMillis(_) + | Value::LocalTimestampMicros(_) + | Value::Duration(_) + | Value::Uuid(_) + | Value::Union(_, _)) => { + bail_uncategorized!( + "unimplemented conversion from avro to jsonb: {:?}", + ValueKind::from(v) + ) + } + } + Ok(()) +} + #[cfg(test)] mod tests { + use std::str::FromStr; + use apache_avro::Decimal as AvroDecimal; - use risingwave_common::types::{Decimal, Timestamptz}; + use risingwave_common::types::{Datum, Decimal}; use super::*; @@ -478,18 +524,14 @@ mod tests { /// - string: String /// - Date (the number of days from the unix epoch, 1970-1-1 UTC) /// - Timestamp (the number of milliseconds from the unix epoch, 1970-1-1 00:00:00.000 UTC) - - pub(crate) fn from_avro_value( + fn from_avro_value( value: Value, value_schema: &Schema, shape: &DataType, - ) -> crate::error::ConnectorResult { - AvroParseOptions { - schema: Some(value_schema), - relax_numeric: true, - } - .parse(&value, Some(shape)) - .map_err(Into::into) + ) -> anyhow::Result { + Ok(AvroParseOptions::create(value_schema) + .convert_to_datum(&value, shape)? + .to_owned_datum()) } #[test] @@ -529,8 +571,11 @@ mod tests { .unwrap(); let bytes = vec![0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f]; let value = Value::Decimal(AvroDecimal::from(bytes)); - let options = AvroParseOptions::default().with_schema(&schema); - let resp = options.parse(&value, Some(&DataType::Decimal)).unwrap(); + let options = AvroParseOptions::create(&schema); + let resp = options + .convert_to_datum(&value, &DataType::Decimal) + .unwrap() + .to_owned_datum(); assert_eq!( resp, Some(ScalarImpl::Decimal(Decimal::Normalized( @@ -566,8 +611,11 @@ mod tests { ("value".to_string(), Value::Bytes(vec![0x01, 0x02, 0x03])), ]); - let options = AvroParseOptions::default().with_schema(&schema); - let resp = options.parse(&value, Some(&DataType::Decimal)).unwrap(); + let options = AvroParseOptions::create(&schema); + let resp = options + .convert_to_datum(&value, &DataType::Decimal) + .unwrap() + .to_owned_datum(); assert_eq!(resp, Some(ScalarImpl::Decimal(Decimal::from(66051)))); } } diff --git a/src/connector/codec/src/decoder/avro/schema.rs b/src/connector/codec/src/decoder/avro/schema.rs new file mode 100644 index 0000000000000..fe96495d089ea --- /dev/null +++ b/src/connector/codec/src/decoder/avro/schema.rs @@ -0,0 +1,282 @@ +// Copyright 2024 RisingWave Labs +// +// 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, LazyLock}; + +use apache_avro::schema::{DecimalSchema, RecordSchema, ResolvedSchema, Schema}; +use apache_avro::AvroResult; +use itertools::Itertools; +use risingwave_common::bail; +use risingwave_common::log::LogSuppresser; +use risingwave_common::types::{DataType, Decimal}; +use risingwave_pb::plan_common::{AdditionalColumn, ColumnDesc, ColumnDescVersion}; + +/// Avro schema with `Ref` inlined. The newtype is used to indicate whether the schema is resolved. +/// +/// TODO: Actually most of the place should use resolved schema, but currently they just happen to work (Some edge cases are not met yet). +/// +/// TODO: refactor avro lib to use the feature there. +#[derive(Debug)] +pub struct ResolvedAvroSchema { + /// Should be used for parsing bytes into Avro value + pub original_schema: Arc, + /// Should be used for type mapping from Avro value to RisingWave datum + pub resolved_schema: Schema, +} + +impl ResolvedAvroSchema { + pub fn create(schema: Arc) -> AvroResult { + let resolver = ResolvedSchema::try_from(schema.as_ref())?; + // todo: to_resolved may cause stackoverflow if there's a loop in the schema + let resolved_schema = resolver.to_resolved(schema.as_ref())?; + Ok(Self { + original_schema: schema, + resolved_schema, + }) + } +} + +/// How to convert the map type from the input encoding to RisingWave's datatype. +/// +/// XXX: Should this be `avro.map.handling.mode`? Can it be shared between Avro and Protobuf? +#[derive(Debug, Copy, Clone)] +pub enum MapHandling { + Jsonb, + // TODO: + // Map +} + +impl MapHandling { + pub const OPTION_KEY: &'static str = "map.handling.mode"; + + pub fn from_options( + options: &std::collections::BTreeMap, + ) -> anyhow::Result> { + let mode = match options.get(Self::OPTION_KEY).map(std::ops::Deref::deref) { + Some("jsonb") => Self::Jsonb, + Some(v) => bail!("unrecognized {} value {}", Self::OPTION_KEY, v), + None => return Ok(None), + }; + Ok(Some(mode)) + } +} + +/// This function expects resolved schema (no `Ref`). +/// FIXME: require passing resolved schema here. +/// TODO: change `map_handling` to some `Config`, and also unify debezium. +/// TODO: use `ColumnDesc` in common instead of PB. +pub fn avro_schema_to_column_descs( + schema: &Schema, + map_handling: Option, +) -> anyhow::Result> { + if let Schema::Record(RecordSchema { fields, .. }) = schema { + let mut index = 0; + let fields = fields + .iter() + .map(|field| { + avro_field_to_column_desc(&field.name, &field.schema, &mut index, map_handling) + }) + .collect::>()?; + Ok(fields) + } else { + bail!("schema invalid, record type required at top level of the schema."); + } +} + +const DBZ_VARIABLE_SCALE_DECIMAL_NAME: &str = "VariableScaleDecimal"; +const DBZ_VARIABLE_SCALE_DECIMAL_NAMESPACE: &str = "io.debezium.data"; + +fn avro_field_to_column_desc( + name: &str, + schema: &Schema, + index: &mut i32, + map_handling: Option, +) -> anyhow::Result { + let data_type = avro_type_mapping(schema, map_handling)?; + match schema { + Schema::Record(RecordSchema { + name: schema_name, + fields, + .. + }) => { + let vec_column = fields + .iter() + .map(|f| avro_field_to_column_desc(&f.name, &f.schema, index, map_handling)) + .collect::>()?; + *index += 1; + Ok(ColumnDesc { + column_type: Some(data_type.to_protobuf()), + column_id: *index, + name: name.to_owned(), + field_descs: vec_column, + type_name: schema_name.to_string(), + generated_or_default_column: None, + description: None, + additional_column_type: 0, // deprecated + additional_column: Some(AdditionalColumn { column_type: None }), + version: ColumnDescVersion::Pr13707 as i32, + }) + } + _ => { + *index += 1; + Ok(ColumnDesc { + column_type: Some(data_type.to_protobuf()), + column_id: *index, + name: name.to_owned(), + additional_column: Some(AdditionalColumn { column_type: None }), + version: ColumnDescVersion::Pr13707 as i32, + ..Default::default() + }) + } + } +} + +/// This function expects resolved schema (no `Ref`). +fn avro_type_mapping( + schema: &Schema, + map_handling: Option, +) -> anyhow::Result { + let data_type = match schema { + Schema::String => DataType::Varchar, + Schema::Int => DataType::Int32, + Schema::Long => DataType::Int64, + Schema::Boolean => DataType::Boolean, + Schema::Float => DataType::Float32, + Schema::Double => DataType::Float64, + Schema::Decimal(DecimalSchema { precision, .. }) => { + if *precision > Decimal::MAX_PRECISION.into() { + static LOG_SUPPERSSER: LazyLock = + LazyLock::new(LogSuppresser::default); + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + suppressed_count, + "RisingWave supports decimal precision up to {}, but got {}. Will truncate.", + Decimal::MAX_PRECISION, + precision + ); + } + } + DataType::Decimal + } + Schema::Date => DataType::Date, + Schema::LocalTimestampMillis => DataType::Timestamp, + Schema::LocalTimestampMicros => DataType::Timestamp, + Schema::TimestampMillis => DataType::Timestamptz, + Schema::TimestampMicros => DataType::Timestamptz, + Schema::Duration => DataType::Interval, + Schema::Bytes => DataType::Bytea, + Schema::Enum { .. } => DataType::Varchar, + Schema::TimeMillis => DataType::Time, + Schema::TimeMicros => DataType::Time, + Schema::Record(RecordSchema { fields, name, .. }) => { + if name.name == DBZ_VARIABLE_SCALE_DECIMAL_NAME + && name.namespace == Some(DBZ_VARIABLE_SCALE_DECIMAL_NAMESPACE.into()) + { + return Ok(DataType::Decimal); + } + + let struct_fields = fields + .iter() + .map(|f| avro_type_mapping(&f.schema, map_handling)) + .collect::>()?; + let struct_names = fields.iter().map(|f| f.name.clone()).collect_vec(); + DataType::new_struct(struct_fields, struct_names) + } + Schema::Array(item_schema) => { + let item_type = avro_type_mapping(item_schema.as_ref(), map_handling)?; + DataType::List(Box::new(item_type)) + } + Schema::Union(union_schema) => { + // We only support using union to represent nullable fields, not general unions. + let variants = union_schema.variants(); + if variants.len() != 2 || !variants.contains(&Schema::Null) { + bail!( + "unsupported Avro type, only unions like [null, T] is supported: {:?}", + schema + ); + } + let nested_schema = variants + .iter() + .find_or_first(|s| !matches!(s, Schema::Null)) + .unwrap(); + + avro_type_mapping(nested_schema, map_handling)? + } + Schema::Ref { name } => { + if name.name == DBZ_VARIABLE_SCALE_DECIMAL_NAME + && name.namespace == Some(DBZ_VARIABLE_SCALE_DECIMAL_NAMESPACE.into()) + { + DataType::Decimal + } else { + bail!("unsupported Avro type: {:?}", schema); + } + } + Schema::Map(value_schema) => { + // TODO: support native map type + match map_handling { + Some(MapHandling::Jsonb) => { + if supported_avro_to_json_type(value_schema) { + DataType::Jsonb + } else { + bail!( + "unsupported Avro type, cannot convert map to jsonb: {:?}", + schema + ) + } + } + None => { + bail!("`map.handling.mode` not specified in ENCODE AVRO (...). Currently supported modes: `jsonb`") + } + } + } + Schema::Uuid => DataType::Varchar, + Schema::Null | Schema::Fixed(_) => { + bail!("unsupported Avro type: {:?}", schema) + } + }; + + Ok(data_type) +} + +/// Check for [`super::avro_to_jsonb`] +fn supported_avro_to_json_type(schema: &Schema) -> bool { + match schema { + Schema::Null | Schema::Boolean | Schema::Int | Schema::String => true, + + Schema::Map(value_schema) | Schema::Array(value_schema) => { + supported_avro_to_json_type(value_schema) + } + Schema::Record(RecordSchema { fields, .. }) => fields + .iter() + .all(|f| supported_avro_to_json_type(&f.schema)), + Schema::Long + | Schema::Float + | Schema::Double + | Schema::Bytes + | Schema::Enum(_) + | Schema::Fixed(_) + | Schema::Decimal(_) + | Schema::Uuid + | Schema::Date + | Schema::TimeMillis + | Schema::TimeMicros + | Schema::TimestampMillis + | Schema::TimestampMicros + | Schema::LocalTimestampMillis + | Schema::LocalTimestampMicros + | Schema::Duration + | Schema::Ref { name: _ } + | Schema::Union(_) => false, + } +} diff --git a/src/connector/codec/src/decoder/json/mod.rs b/src/connector/codec/src/decoder/json/mod.rs new file mode 100644 index 0000000000000..b56ce3abbee25 --- /dev/null +++ b/src/connector/codec/src/decoder/json/mod.rs @@ -0,0 +1,35 @@ +// Copyright 2024 RisingWave Labs +// +// 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 anyhow::Context; +use risingwave_pb::plan_common::ColumnDesc; + +use super::avro::{avro_schema_to_column_descs, MapHandling}; + +impl crate::JsonSchema { + /// FIXME: when the JSON schema is invalid, it will panic. + /// + /// ## Notes on type conversion + /// Map will be used when an object doesn't have `properties` but has `additionalProperties`. + /// When an object has `properties` and `additionalProperties`, the latter will be ignored. + /// + /// + /// TODO: examine other stuff like `oneOf`, `patternProperties`, etc. + pub fn json_schema_to_columns(&self) -> anyhow::Result> { + let avro_schema = jst::convert_avro(&self.0, jst::Context::default()).to_string(); + let schema = + apache_avro::Schema::parse_str(&avro_schema).context("failed to parse avro schema")?; + avro_schema_to_column_descs(&schema, Some(MapHandling::Jsonb)).map_err(Into::into) + } +} diff --git a/src/connector/codec/src/decoder/mod.rs b/src/connector/codec/src/decoder/mod.rs new file mode 100644 index 0000000000000..c7e04ab210a6e --- /dev/null +++ b/src/connector/codec/src/decoder/mod.rs @@ -0,0 +1,80 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +pub mod avro; +pub mod json; +pub mod utils; + +use risingwave_common::types::{DataType, Datum, DatumCow, ToOwnedDatum}; +use thiserror::Error; +use thiserror_ext::Macro; + +#[derive(Error, Debug, Macro)] +#[thiserror_ext(macro(mangle, path = "crate::decoder"))] +pub enum AccessError { + #[error("Undefined field `{name}` at `{path}`")] + Undefined { name: String, path: String }, + #[error("Cannot parse value `{value}` with type `{got}` into expected type `{expected}`")] + TypeError { + expected: String, + got: String, + value: String, + }, + #[error("Unsupported data type `{ty}`")] + UnsupportedType { ty: String }, + + #[error("Unsupported additional column `{name}`")] + UnsupportedAdditionalColumn { name: String }, + + /// Errors that are not categorized into variants above. + #[error("{message}")] + Uncategorized { message: String }, +} + +pub type AccessResult = std::result::Result; + +/// Access to a field in the data structure. Created by `AccessBuilder`. +pub trait Access { + /// Accesses `path` in the data structure (*parsed* Avro/JSON/Protobuf data), + /// and then converts it to RisingWave `Datum`. + /// `type_expected` might or might not be used during the conversion depending on the implementation. + /// + /// # Path + /// + /// We usually expect the data is a record (struct), and `path` represents field path. + /// The data (or part of the data) represents the whole row (`Vec`), + /// and we use different `path` to access one column at a time. + /// + /// e.g., for Avro, we access `["col_name"]`; for Debezium Avro, we access `["before", "col_name"]`. + /// + /// # Returns + /// + /// The implementation should prefer to return a borrowed [`DatumRef`](risingwave_common::types::DatumRef) + /// through [`DatumCow::Borrowed`] to avoid unnecessary allocation if possible, especially for fields + /// with string or bytes data. If that's not the case, it may return an owned [`Datum`] through + /// [`DatumCow::Owned`]. + fn access<'a>(&'a self, path: &[&str], type_expected: &DataType) -> AccessResult>; +} + +// Note: made an extension trait to disallow implementing or overriding `access_owned`. +#[easy_ext::ext(AccessExt)] +impl A { + /// Similar to `access`, but always returns an owned [`Datum`]. See [`Access::access`] for more details. + /// + /// Always prefer calling `access` directly if possible to avoid unnecessary allocation. + pub fn access_owned(&self, path: &[&str], type_expected: &DataType) -> AccessResult { + self.access(path, type_expected) + .map(ToOwnedDatum::to_owned_datum) + } +} diff --git a/src/connector/codec/src/decoder/utils.rs b/src/connector/codec/src/decoder/utils.rs new file mode 100644 index 0000000000000..d4ce4b5ed8970 --- /dev/null +++ b/src/connector/codec/src/decoder/utils.rs @@ -0,0 +1,52 @@ +// Copyright 2024 RisingWave Labs +// +// 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 super::{bail_uncategorized, AccessResult}; + +pub fn extract_decimal(bytes: Vec) -> AccessResult<(u32, u32, u32)> { + match bytes.len() { + len @ 0..=4 => { + let mut pad = vec![0; 4 - len]; + pad.extend_from_slice(&bytes); + let lo = u32::from_be_bytes(pad.try_into().unwrap()); + Ok((lo, 0, 0)) + } + len @ 5..=8 => { + let zero_len = 8 - len; + let mid_end = 4 - zero_len; + + let mut pad = vec![0; zero_len]; + pad.extend_from_slice(&bytes[..mid_end]); + let mid = u32::from_be_bytes(pad.try_into().unwrap()); + + let lo = u32::from_be_bytes(bytes[mid_end..].to_owned().try_into().unwrap()); + Ok((lo, mid, 0)) + } + len @ 9..=12 => { + let zero_len = 12 - len; + let hi_end = 4 - zero_len; + let mid_end = hi_end + 4; + + let mut pad = vec![0; zero_len]; + pad.extend_from_slice(&bytes[..hi_end]); + let hi = u32::from_be_bytes(pad.try_into().unwrap()); + + let mid = u32::from_be_bytes(bytes[hi_end..mid_end].to_owned().try_into().unwrap()); + + let lo = u32::from_be_bytes(bytes[mid_end..].to_owned().try_into().unwrap()); + Ok((lo, mid, hi)) + } + _ => bail_uncategorized!("invalid decimal bytes length {}", bytes.len()), + } +} diff --git a/src/connector/codec/src/lib.rs b/src/connector/codec/src/lib.rs new file mode 100644 index 0000000000000..651fa84e109fb --- /dev/null +++ b/src/connector/codec/src/lib.rs @@ -0,0 +1,64 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +//! Encoding and decoding between external data formats and RisingWave datum (i.e., type mappings). + +#![allow(clippy::derive_partial_eq_without_eq)] +#![feature(array_chunks)] +#![feature(coroutines)] +#![feature(proc_macro_hygiene)] +#![feature(stmt_expr_attributes)] +#![feature(box_patterns)] +#![feature(trait_alias)] +#![feature(lint_reasons)] +#![feature(lazy_cell)] +#![feature(let_chains)] +#![feature(box_into_inner)] +#![feature(type_alias_impl_trait)] +#![feature(associated_type_defaults)] +#![feature(impl_trait_in_assoc_type)] +#![feature(iter_from_coroutine)] +#![feature(if_let_guard)] +#![feature(iterator_try_collect)] +#![feature(try_blocks)] +#![feature(error_generic_member_access)] +#![feature(negative_impls)] +#![feature(register_tool)] +#![feature(assert_matches)] +#![register_tool(rw)] +#![recursion_limit = "256"] + +/// Converts JSON/AVRO/Protobuf data to RisingWave datum. +/// The core API is [`decoder::Access`]. +pub mod decoder; + +pub use apache_avro::schema::Schema as AvroSchema; +pub use apache_avro::types::{Value as AvroValue, ValueKind as AvroValueKind}; +pub use risingwave_pb::plan_common::ColumnDesc; +pub struct JsonSchema(pub serde_json::Value); +impl JsonSchema { + pub fn parse_str(schema: &str) -> anyhow::Result { + use anyhow::Context; + + let value = serde_json::from_str(schema).context("failed to parse json schema")?; + Ok(Self(value)) + } + + pub fn parse_bytes(schema: &[u8]) -> anyhow::Result { + use anyhow::Context; + + let value = serde_json::from_slice(schema).context("failed to parse json schema")?; + Ok(Self(value)) + } +} diff --git a/src/connector/codec/tests/integration_tests/avro.rs b/src/connector/codec/tests/integration_tests/avro.rs new file mode 100644 index 0000000000000..fab143b2bf9e7 --- /dev/null +++ b/src/connector/codec/tests/integration_tests/avro.rs @@ -0,0 +1,556 @@ +// Copyright 2024 RisingWave Labs +// +// 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 apache_avro::from_avro_datum; +use risingwave_connector_codec::decoder::avro::{ + avro_schema_to_column_descs, AvroAccess, AvroParseOptions, MapHandling, ResolvedAvroSchema, +}; +use risingwave_connector_codec::decoder::Access; +use risingwave_connector_codec::AvroSchema; + +use crate::utils::*; + +/// Refer to `AvroAccessBuilder::parse_avro_value` for how Avro data looks like. +enum TestDataEncoding { + /// Each data is a JSON encoded Avro value. + /// + /// Refer to: + /// + /// Specially, it handles `union` variants, and differentiates `bytes` from `string`. + /// + /// TODO: Not supported yet, because `apache_avro` doesn't support decoding JSON encoded avro.. + #[allow(dead_code)] + Json, + /// Each data is a binary encoded Avro value, converted to a hex string. + /// + /// Tool convert Avro JSON to hex string: + HexBinary, +} + +struct Config { + /// TODO: For one test data, we can test all possible config options. + map_handling: Option, + data_encoding: TestDataEncoding, +} + +/// Data driven testing for converting Avro Schema to RisingWave Schema, and then converting Avro data into RisingWave data. +/// +/// The expected results can be automatically updated. To run and update the tests: +/// ```bash +/// UPDATE_EXPECT=1 cargo test -p risingwave_connector_codec +/// ``` +/// Or use Rust Analyzer. Refer to . +/// +/// ## Arguments +/// - `avro_schema`: Avro schema in JSON format. +/// - `avro_data`: list of Avro data. Refer to [`TestDataEncoding`] for the format. +/// +/// ## Why not directly test the uppermost layer `AvroParserConfig` and `AvroAccessBuilder`? +/// +/// Because their interface are not clean enough, and have complex logic like schema registry. +/// We might need to separate logic to make them clenaer and then we can use it directly for testing. +/// +/// ## If we reimplement a similar logic here, what are we testing? +/// +/// Basically unit tests of `avro_schema_to_column_descs`, `convert_to_datum`, i.e., the type mapping. +/// +/// It makes some sense, as the data parsing logic is generally quite simple (one-liner), and the most +/// complex and error-prone part is the type mapping. +/// +/// ## Why test schema mapping and data mapping together? +/// +/// Because the expected data type for data mapping comes from the schema mapping. +#[track_caller] +fn check( + avro_schema: &str, + avro_data: &[&str], + config: Config, + expected_risingwave_schema: expect_test::Expect, + expected_risingwave_data: expect_test::Expect, +) { + // manually implement some logic in AvroParserConfig::map_to_columns + let avro_schema = AvroSchema::parse_str(avro_schema).expect("failed to parse Avro schema"); + let resolved_schema = + ResolvedAvroSchema::create(avro_schema.into()).expect("failed to resolve Avro schema"); + + let rw_schema = + avro_schema_to_column_descs(&resolved_schema.resolved_schema, config.map_handling) + .expect("failed to convert Avro schema to RisingWave schema") + .iter() + .map(ColumnDesc::from) + .collect_vec(); + expected_risingwave_schema.assert_eq(&format!( + "{:#?}", + rw_schema.iter().map(ColumnDescTestDisplay).collect_vec() + )); + + // manually implement some logic in AvroAccessBuilder, and some in PlainParser::parse_inner + let mut data_str = vec![]; + for data in avro_data { + let parser = AvroParseOptions::create(&resolved_schema.resolved_schema); + + match config.data_encoding { + TestDataEncoding::Json => todo!(), + TestDataEncoding::HexBinary => { + let data = hex::decode(data).expect("failed to decode hex string"); + let avro_data = + from_avro_datum(&resolved_schema.original_schema, &mut data.as_slice(), None) + .expect("failed to parse Avro data"); + let access = AvroAccess::new(&avro_data, parser); + + let mut row = vec![]; + for col in &rw_schema { + let rw_data = access + .access(&[&col.name], &col.data_type) + .expect("failed to access"); + row.push(format!("{:#?}", DatumCowTestDisplay(&rw_data))); + } + data_str.push(format!("{}", row.iter().format("\n"))); + } + } + } + expected_risingwave_data.assert_eq(&format!("{}", data_str.iter().format("\n----\n"))); +} + +// This corresponds to legacy `scripts/source/test_data/avro_simple_schema_bin.1`. TODO: remove that file. +#[test] +fn test_simple() { + check( + r#" +{ + "name": "test_student", + "type": "record", + "fields": [ + { + "name": "id", + "type": "int", + "default": 0 + }, + { + "name": "sequence_id", + "type": "long", + "default": 0 + }, + { + "name": "name", + "type": ["null", "string"] + }, + { + "name": "score", + "type": "float", + "default": 0.0 + }, + { + "name": "avg_score", + "type": "double", + "default": 0.0 + }, + { + "name": "is_lasted", + "type": "boolean", + "default": false + }, + { + "name": "entrance_date", + "type": "int", + "logicalType": "date", + "default": 0 + }, + { + "name": "birthday", + "type": "long", + "logicalType": "timestamp-millis", + "default": 0 + }, + { + "name": "anniversary", + "type": "long", + "logicalType": "timestamp-micros", + "default": 0 + }, + { + "name": "passed", + "type": { + "name": "interval", + "type": "fixed", + "size": 12 + }, + "logicalType": "duration" + }, + { + "name": "bytes", + "type": "bytes", + "default": "" + } + ] +} + "#, + &[ + // {"id":32,"sequence_id":64,"name":{"string":"str_value"},"score":32.0,"avg_score":64.0,"is_lasted":true,"entrance_date":0,"birthday":0,"anniversary":0,"passed":"\u0001\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u00E8\u0003\u0000\u0000","bytes":"\u0001\u0002\u0003\u0004\u0005"} + "40800102127374725f76616c7565000000420000000000005040010000000100000001000000e80300000a0102030405" + ], + Config { + map_handling: None, + data_encoding: TestDataEncoding::HexBinary, + }, + expect![[r#" + [ + id(#1): Int32, + sequence_id(#2): Int64, + name(#3): Varchar, + score(#4): Float32, + avg_score(#5): Float64, + is_lasted(#6): Boolean, + entrance_date(#7): Date, + birthday(#8): Timestamptz, + anniversary(#9): Timestamptz, + passed(#10): Interval, + bytes(#11): Bytea, + ]"#]], + expect![[r#" + Owned(Int32(32)) + Owned(Int64(64)) + Borrowed(Utf8("str_value")) + Owned(Float32(OrderedFloat(32.0))) + Owned(Float64(OrderedFloat(64.0))) + Owned(Bool(true)) + Owned(Date(Date(1970-01-01))) + Owned(Timestamptz(Timestamptz(0))) + Owned(Timestamptz(Timestamptz(0))) + Owned(Interval(Interval { months: 1, days: 1, usecs: 1000000 })) + Borrowed(Bytea([1, 2, 3, 4, 5]))"#]], + ) +} + +#[test] +fn test_nullable_union() { + check( + r#" +{ + "name": "test_student", + "type": "record", + "fields": [ + { + "name": "id", + "type": "int", + "default": 0 + }, + { + "name": "age", + "type": ["null", "int"] + }, + { + "name": "sequence_id", + "type": ["null", "long"] + }, + { + "name": "name", + "type": ["null", "string"], + "default": null + }, + { + "name": "score", + "type": [ "float", "null" ], + "default": 1.0 + }, + { + "name": "avg_score", + "type": ["null", "double"] + }, + { + "name": "is_lasted", + "type": ["null", "boolean"] + }, + { + "name": "entrance_date", + "type": [ + "null", + { + "type": "int", + "logicalType": "date", + "arg.properties": { + "range": { + "min": 1, + "max": 19374 + } + } + } + ], + "default": null + }, + { + "name": "birthday", + "type": [ + "null", + { + "type": "long", + "logicalType": "timestamp-millis", + "arg.properties": { + "range": { + "min": 1, + "max": 1673970376213 + } + } + } + ], + "default": null + }, + { + "name": "anniversary", + "type": [ + "null", + { + "type" : "long", + "logicalType": "timestamp-micros", + "arg.properties": { + "range": { + "min": 1, + "max": 1673970376213000 + } + } + } + ], + "default": null + } + ] +} + "#, + &[ + // { + // "id": 5, + // "age": null, + // "sequence_id": null, + // "name": null, + // "score": null, + // "avg_score": null, + // "is_lasted": null, + // "entrance_date": null, + // "birthday": null, + // "anniversary": null + // } + "0a000000020000000000", + ], + Config { + map_handling: None, + data_encoding: TestDataEncoding::HexBinary, + }, + expect![[r#" + [ + id(#1): Int32, + age(#2): Int32, + sequence_id(#3): Int64, + name(#4): Varchar, + score(#5): Float32, + avg_score(#6): Float64, + is_lasted(#7): Boolean, + entrance_date(#8): Date, + birthday(#9): Timestamptz, + anniversary(#10): Timestamptz, + ]"#]], + expect![[r#" + Owned(Int32(5)) + Owned(null) + Owned(null) + Owned(null) + Owned(null) + Owned(null) + Owned(null) + Owned(null) + Owned(null) + Owned(null)"#]], + ) +} + +/// From `e2e_test/source_inline/kafka/avro/upsert_avro_json` +#[test] +fn test_1() { + check( + r#" +{ + "type": "record", + "name": "OBJ_ATTRIBUTE_VALUE", + "namespace": "CPLM", + "fields": [ + { + "name": "op_type", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "ID", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "CLASS_ID", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "ITEM_ID", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "ATTR_ID", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "ATTR_VALUE", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "ORG_ID", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "UNIT_INFO", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "UPD_TIME", + "type": [ + "null", + "string" + ], + "default": null + }, + { + "name": "DEC_VAL", + "type": [ + { + "type": "bytes", + "logicalType": "decimal", + "precision": 10, + "scale": 2 + }, + "null" + ], + "default": "\u00ff" + }, + { + "name": "REFERRED", + "type": [ + "null", + { + "type": "record", + "name": "REFERRED_TYPE", + "fields": [ + { + "name": "a", + "type": "string" + } + ] + } + ], + "default": null + }, + { + "name": "REF", + "type": [ + "null", + "REFERRED_TYPE" + ], + "default": null + }, + { + "name": "uuid", + "type": [ + "null", + { + "type": "string", + "logicalType": "uuid" + } + ], + "default": null + }, + { + "name": "rate", + "type": "double", + "default": "NaN" + } + ], + "connect.name": "CPLM.OBJ_ATTRIBUTE_VALUE" +} +"#, + &[ + // {"op_type": {"string": "update"}, "ID": {"string": "id1"}, "CLASS_ID": {"string": "1"}, "ITEM_ID": {"string": "6768"}, "ATTR_ID": {"string": "6970"}, "ATTR_VALUE": {"string": "value9"}, "ORG_ID": {"string": "7172"}, "UNIT_INFO": {"string": "info9"}, "UPD_TIME": {"string": "2021-05-18T07:59:58.714Z"}, "DEC_VAL": {"bytes": "\u0002\u0054\u000b\u00e3\u00ff"}} + "020c7570646174650206696431020231020836373638020836393730020c76616c756539020837313732020a696e666f390230323032312d30352d31385430373a35393a35382e3731345a000a02540be3ff000000000000000000f87f" + ], + Config { + map_handling: None, + data_encoding: TestDataEncoding::HexBinary, + }, + expect![[r#" + [ + op_type(#1): Varchar, + ID(#2): Varchar, + CLASS_ID(#3): Varchar, + ITEM_ID(#4): Varchar, + ATTR_ID(#5): Varchar, + ATTR_VALUE(#6): Varchar, + ORG_ID(#7): Varchar, + UNIT_INFO(#8): Varchar, + UPD_TIME(#9): Varchar, + DEC_VAL(#10): Decimal, + REFERRED(#11): Struct { a: Varchar }, + REF(#12): Struct { a: Varchar }, + uuid(#13): Varchar, + rate(#14): Float64, + ]"#]], + expect![[r#" + Borrowed(Utf8("update")) + Borrowed(Utf8("id1")) + Borrowed(Utf8("1")) + Borrowed(Utf8("6768")) + Borrowed(Utf8("6970")) + Borrowed(Utf8("value9")) + Borrowed(Utf8("7172")) + Borrowed(Utf8("info9")) + Borrowed(Utf8("2021-05-18T07:59:58.714Z")) + Owned(Decimal(Normalized(99999999.99))) + Owned(null) + Owned(null) + Owned(null) + Owned(Float64(OrderedFloat(NaN)))"#]], + ); +} diff --git a/src/storage/src/hummock/file_cache/mod.rs b/src/connector/codec/tests/integration_tests/main.rs similarity index 88% rename from src/storage/src/hummock/file_cache/mod.rs rename to src/connector/codec/tests/integration_tests/main.rs index 2f2ac826410b0..8c718f918d0a6 100644 --- a/src/storage/src/hummock/file_cache/mod.rs +++ b/src/connector/codec/tests/integration_tests/main.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod recent_filter; -mod store; +mod avro; -pub use recent_filter::*; -pub use store::*; +pub mod utils; diff --git a/src/connector/codec/tests/integration_tests/utils.rs b/src/connector/codec/tests/integration_tests/utils.rs new file mode 100644 index 0000000000000..cecb0796c455a --- /dev/null +++ b/src/connector/codec/tests/integration_tests/utils.rs @@ -0,0 +1,182 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +pub use expect_test::{expect, Expect}; +pub use itertools::Itertools; +pub use risingwave_common::catalog::ColumnDesc; +use risingwave_common::types::{ + DataType, Datum, DatumCow, DatumRef, ScalarImpl, ScalarRefImpl, ToDatumRef, +}; +use risingwave_pb::plan_common::AdditionalColumn; + +/// More concise display for `DataType`, to use in tests. +pub struct DataTypeTestDisplay<'a>(pub &'a DataType); + +impl<'a> std::fmt::Debug for DataTypeTestDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + DataType::Struct(s) => { + if s.len() == 1 { + // avoid multiline display for single field struct + let (name, ty) = s.iter().next().unwrap(); + return write!(f, "Struct {{ {}: {:?} }}", name, &DataTypeTestDisplay(ty)); + } + + let mut f = f.debug_struct("Struct"); + for (name, ty) in s.iter() { + f.field(name, &DataTypeTestDisplay(ty)); + } + f.finish()?; + Ok(()) + } + DataType::List(t) => f + .debug_tuple("List") + .field(&DataTypeTestDisplay(t)) + .finish(), + _ => { + // do not use alternative display for simple types + write!(f, "{:?}", self.0) + } + } + } +} + +/// More concise display for `ScalarRefImpl`, to use in tests. +pub struct ScalarRefImplTestDisplay<'a>(pub ScalarRefImpl<'a>); + +impl<'a> std::fmt::Debug for ScalarRefImplTestDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + ScalarRefImpl::Struct(s) => { + if s.iter_fields_ref().len() == 1 { + // avoid multiline display for single field struct + let field = s.iter_fields_ref().next().unwrap(); + return write!(f, "StructValue({:#?})", &DatumRefTestDisplay(field)); + } + + let mut f = f.debug_tuple("StructValue"); + for field in s.iter_fields_ref() { + f.field(&DatumRefTestDisplay(field)); + } + f.finish()?; + Ok(()) + } + ScalarRefImpl::List(l) => f + .debug_list() + .entries(l.iter().map(DatumRefTestDisplay)) + .finish(), + _ => { + // do not use alternative display for simple types + write!(f, "{:?}", self.0) + } + } + } +} + +/// More concise display for `ScalarImpl`, to use in tests. +pub struct ScalarImplTestDisplay<'a>(pub &'a ScalarImpl); + +impl<'a> std::fmt::Debug for ScalarImplTestDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ScalarRefImplTestDisplay(self.0.as_scalar_ref_impl()).fmt(f) + } +} + +/// More concise display for `DatumRef`, to use in tests. +pub struct DatumRefTestDisplay<'a>(pub DatumRef<'a>); + +impl<'a> std::fmt::Debug for DatumRefTestDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + Some(scalar) => ScalarRefImplTestDisplay(scalar).fmt(f), + None => write!(f, "null"), + } + } +} + +/// More concise display for `Datum`, to use in tests. +pub struct DatumTestDisplay<'a>(pub &'a Datum); + +impl<'a> std::fmt::Debug for DatumTestDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + DatumRefTestDisplay(self.0.to_datum_ref()).fmt(f) + } +} + +/// More concise display for `DatumCow`, to use in tests. +pub struct DatumCowTestDisplay<'a>(pub &'a DatumCow<'a>); + +impl<'a> std::fmt::Debug for DatumCowTestDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + DatumCow::Borrowed(datum_ref) => { + // don't use debug_tuple to avoid extra newline + write!(f, "Borrowed(")?; + DatumRefTestDisplay(*datum_ref).fmt(f)?; + write!(f, ")")?; + } + DatumCow::Owned(datum) => { + write!(f, "Owned(")?; + DatumTestDisplay(datum).fmt(f)?; + write!(f, ")")?; + } + } + Ok(()) + } +} + +/// More concise display for `ColumnDesc`, to use in tests. +pub struct ColumnDescTestDisplay<'a>(pub &'a ColumnDesc); + +impl<'a> std::fmt::Debug for ColumnDescTestDisplay<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ColumnDesc { + data_type, + column_id, + name, + field_descs, + type_name, + generated_or_default_column, + description, + additional_column: AdditionalColumn { column_type }, + version: _, + } = &self.0; + + write!( + f, + "{name}(#{column_id}): {:#?}", + DataTypeTestDisplay(data_type) + )?; + if !type_name.is_empty() { + write!(f, ", type_name: {}", type_name)?; + } + if !field_descs.is_empty() { + write!(f, ", field_descs: {:?}", field_descs)?; + } + if let Some(generated_or_default_column) = generated_or_default_column { + write!( + f, + ", generated_or_default_column: {:?}", + generated_or_default_column + )?; + } + if let Some(description) = description { + write!(f, ", description: {:?}", description)?; + } + if let Some(column_type) = column_type { + write!(f, ", additional_column: {:?}", column_type)?; + } + Ok(()) + } +} diff --git a/src/connector/src/connector_common/common.rs b/src/connector/src/connector_common/common.rs index 302b68dd664a1..92bb7d9c30677 100644 --- a/src/connector/src/connector_common/common.rs +++ b/src/connector/src/connector_common/common.rs @@ -13,7 +13,7 @@ // limitations under the License. use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::io::Write; use std::time::Duration; @@ -44,6 +44,8 @@ use crate::source::nats::source::NatsOffset; pub const PRIVATE_LINK_BROKER_REWRITE_MAP_KEY: &str = "broker.rewrite.endpoints"; pub const PRIVATE_LINK_TARGETS_KEY: &str = "privatelink.targets"; +const AWS_MSK_IAM_AUTH: &str = "AWS_MSK_IAM"; + #[derive(Debug, Clone, Deserialize)] pub struct AwsPrivateLinkItem { pub az_id: Option, @@ -59,16 +61,28 @@ use aws_types::SdkConfig; /// A flatten config map for aws auth. #[derive(Deserialize, Debug, Clone, WithOptions)] pub struct AwsAuthProps { + #[serde(rename = "aws.region", alias = "region")] pub region: Option, - #[serde(alias = "endpoint_url")] + + #[serde( + rename = "aws.endpoint_url", + alias = "endpoint_url", + alias = "endpoint" + )] pub endpoint: Option, + #[serde(rename = "aws.credentials.access_key_id", alias = "access_key")] pub access_key: Option, + #[serde(rename = "aws.credentials.secret_access_key", alias = "secret_key")] pub secret_key: Option, + #[serde(rename = "aws.credentials.session_token", alias = "session_token")] pub session_token: Option, + /// IAM role + #[serde(rename = "aws.credentials.role.arn", alias = "arn")] pub arn: Option, - /// This field was added for kinesis. Not sure if it's useful for other connectors. - /// Please ignore it in the documentation for now. + /// external ID in IAM role trust policy + #[serde(rename = "aws.credentials.role.external_id", alias = "external_id")] pub external_id: Option, + #[serde(rename = "aws.profile", alias = "profile")] pub profile: Option, } @@ -181,7 +195,7 @@ pub struct KafkaCommon { #[serde(rename = "properties.ssl.key.password")] ssl_key_password: Option, - /// SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM and GSSAPI. + /// SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM, GSSAPI, and AWS_MSK_IAM. #[serde(rename = "properties.sasl.mechanism")] sasl_mechanism: Option, @@ -224,7 +238,7 @@ pub struct KafkaPrivateLinkCommon { /// This is generated from `private_link_targets` and `private_link_endpoint` in frontend, instead of given by users. #[serde(rename = "broker.rewrite.endpoints")] #[serde_as(as = "Option")] - pub broker_rewrite_map: Option>, + pub broker_rewrite_map: Option>, } const fn default_kafka_sync_call_timeout() -> Duration { @@ -286,6 +300,13 @@ impl RdKafkaPropertiesCommon { impl KafkaCommon { pub(crate) fn set_security_properties(&self, config: &mut ClientConfig) { + // AWS_MSK_IAM + if self.is_aws_msk_iam() { + config.set("security.protocol", "SASL_SSL"); + config.set("sasl.mechanism", "OAUTHBEARER"); + return; + } + // Security protocol if let Some(security_protocol) = self.security_protocol.as_ref() { config.set("security.protocol", security_protocol); @@ -356,6 +377,16 @@ impl KafkaCommon { // Currently, we only support unsecured OAUTH. config.set("enable.sasl.oauthbearer.unsecure.jwt", "true"); } + + pub(crate) fn is_aws_msk_iam(&self) -> bool { + if let Some(sasl_mechanism) = self.sasl_mechanism.as_ref() + && sasl_mechanism == AWS_MSK_IAM_AUTH + { + true + } else { + false + } + } } #[derive(Clone, Debug, Deserialize, WithOptions)] @@ -713,3 +744,24 @@ pub(crate) fn load_private_key( .ok_or_else(|| anyhow!("No private key found"))?; Ok(cert?.into()) } + +#[serde_as] +#[derive(Deserialize, Debug, Clone, WithOptions)] +pub struct MongodbCommon { + /// The URL of MongoDB + #[serde(rename = "mongodb.url")] + pub connect_uri: String, + /// The collection name where data should be written to or read from. For sinks, the format is + /// `db_name.collection_name`. Data can also be written to dynamic collections, see `collection.name.field` + /// for more information. + #[serde(rename = "collection.name")] + pub collection_name: String, +} + +impl MongodbCommon { + pub(crate) async fn build_client(&self) -> ConnectorResult { + let client = mongodb::Client::with_uri_str(&self.connect_uri).await?; + + Ok(client) + } +} diff --git a/src/connector/src/connector_common/mod.rs b/src/connector/src/connector_common/mod.rs index 7a6254c8cde93..4ec36ba78e0be 100644 --- a/src/connector/src/connector_common/mod.rs +++ b/src/connector/src/connector_common/mod.rs @@ -18,6 +18,6 @@ pub use mqtt_common::{MqttCommon, QualityOfService as MqttQualityOfService}; pub mod common; pub use common::{ AwsAuthProps, AwsPrivateLinkItem, KafkaCommon, KafkaPrivateLinkCommon, KinesisCommon, - NatsCommon, PulsarCommon, PulsarOauthCommon, RdKafkaPropertiesCommon, + MongodbCommon, NatsCommon, PulsarCommon, PulsarOauthCommon, RdKafkaPropertiesCommon, PRIVATE_LINK_BROKER_REWRITE_MAP_KEY, PRIVATE_LINK_TARGETS_KEY, }; diff --git a/src/connector/src/error.rs b/src/connector/src/error.rs index 52101d1b0e75e..5bc516c86ba67 100644 --- a/src/connector/src/error.rs +++ b/src/connector/src/error.rs @@ -44,11 +44,13 @@ def_anyhow_newtype! { csv::Error => "failed to parse csv", ArrayError => transparent, + uuid::Error => transparent, // believed to be self-explanatory // Connector errors opendal::Error => transparent, // believed to be self-explanatory parquet::errors::ParquetError => transparent, + sqlx::Error => transparent, // believed to be self-explanatory mysql_async::Error => "MySQL error", tokio_postgres::Error => "Postgres error", apache_avro::Error => "Avro error", @@ -60,12 +62,15 @@ def_anyhow_newtype! { async_nats::jetstream::context::CreateStreamError => "Nats error", async_nats::jetstream::stream::ConsumerError => "Nats error", icelake::Error => "Iceberg error", + iceberg::Error => "IcebergV2 error", redis::RedisError => "Redis error", arrow_schema::ArrowError => "Arrow error", + arrow_schema_iceberg::ArrowError => "Arrow error", google_cloud_pubsub::client::google_cloud_auth::error::Error => "Google Cloud error", rumqttc::tokio_rustls::rustls::Error => "TLS error", rumqttc::v5::ClientError => "MQTT error", rumqttc::v5::OptionError => "MQTT error", + mongodb::error::Error => "Mongodb error", openssl::error::ErrorStack => "OpenSSL error", } diff --git a/src/connector/src/lib.rs b/src/connector/src/lib.rs index f28f3cdce5e77..8c0ade401cb47 100644 --- a/src/connector/src/lib.rs +++ b/src/connector/src/lib.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![expect(dead_code)] #![allow(clippy::derive_partial_eq_without_eq)] #![feature(array_chunks)] #![feature(coroutines)] @@ -34,13 +33,14 @@ #![feature(error_generic_member_access)] #![feature(negative_impls)] #![feature(register_tool)] +#![feature(assert_matches)] +#![feature(never_type)] #![register_tool(rw)] #![recursion_limit = "256"] use std::time::Duration; use duration_str::parse_std; -use risingwave_pb::connector_service::SinkPayloadFormat; use serde::de; pub mod aws_utils; @@ -63,19 +63,6 @@ pub use with_options::WithPropertiesExt; #[cfg(test)] mod with_options_test; -#[derive(Clone, Debug, Default)] -pub struct ConnectorParams { - pub sink_payload_format: SinkPayloadFormat, -} - -impl ConnectorParams { - pub fn new(sink_payload_format: SinkPayloadFormat) -> Self { - Self { - sink_payload_format, - } - } -} - pub(crate) fn deserialize_u32_from_string<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, @@ -175,6 +162,8 @@ mod tests { /// Test some serde behavior we rely on. mod serde { + #![expect(dead_code)] + use std::collections::BTreeMap; use expect_test::expect; diff --git a/src/connector/src/macros.rs b/src/connector/src/macros.rs index b369e6d8a11e3..48f4e16c747b1 100644 --- a/src/connector/src/macros.rs +++ b/src/connector/src/macros.rs @@ -94,7 +94,9 @@ macro_rules! dispatch_source_enum_inner { match $impl { $( $enum_name::$source_variant($inner_name) => { + #[allow(dead_code)] type $prop_type_name = $prop_name; + #[allow(dead_code)] type $split_type_name = $split; { $body @@ -238,6 +240,7 @@ macro_rules! impl_cdc_source_type { } )* + #[derive(Clone)] pub enum CdcSourceType { $( $cdc_source_type, diff --git a/src/connector/src/parser/additional_columns.rs b/src/connector/src/parser/additional_columns.rs index 59c47b06ee8b8..54b1120d06438 100644 --- a/src/connector/src/parser/additional_columns.rs +++ b/src/connector/src/parser/additional_columns.rs @@ -16,18 +16,20 @@ use std::collections::{HashMap, HashSet}; use std::sync::LazyLock; use risingwave_common::bail; -use risingwave_common::catalog::{ColumnCatalog, ColumnDesc, ColumnId}; +use risingwave_common::catalog::{max_column_id, ColumnCatalog, ColumnDesc, ColumnId}; use risingwave_common::types::{DataType, StructType}; use risingwave_pb::data::data_type::TypeName; use risingwave_pb::data::DataType as PbDataType; use risingwave_pb::plan_common::additional_column::ColumnType as AdditionalColumnType; use risingwave_pb::plan_common::{ - AdditionalColumn, AdditionalColumnFilename, AdditionalColumnHeader, AdditionalColumnHeaders, - AdditionalColumnKey, AdditionalColumnOffset, AdditionalColumnPartition, - AdditionalColumnTimestamp, + AdditionalCollectionName, AdditionalColumn, AdditionalColumnFilename, AdditionalColumnHeader, + AdditionalColumnHeaders, AdditionalColumnKey, AdditionalColumnOffset, + AdditionalColumnPartition, AdditionalColumnTimestamp, AdditionalDatabaseName, + AdditionalSchemaName, AdditionalTableName, }; use crate::error::ConnectorResult; +use crate::source::cdc::MONGODB_CDC_CONNECTOR; use crate::source::{ GCS_CONNECTOR, KAFKA_CONNECTOR, KINESIS_CONNECTOR, OPENDAL_S3_CONNECTOR, PULSAR_CONNECTOR, S3_CONNECTOR, @@ -55,9 +57,42 @@ pub static COMPATIBLE_ADDITIONAL_COLUMNS: LazyLock>> = + LazyLock::new(|| { + Some(HashSet::from([ + "timestamp", + "database_name", + "schema_name", + "table_name", + ])) + }); + +pub fn get_supported_additional_columns( + connector_name: &str, + is_cdc_backfill: bool, +) -> Option<&HashSet<&'static str>> { + if is_cdc_backfill { + CDC_BACKFILL_TABLE_ADDITIONAL_COLUMNS.as_ref() + } else { + COMPATIBLE_ADDITIONAL_COLUMNS.get(connector_name) + } +} + pub fn gen_default_addition_col_name( connector_name: &str, additional_col_type: &str, @@ -79,7 +114,7 @@ pub fn gen_default_addition_col_name( }) } -pub fn build_additional_column_catalog( +pub fn build_additional_column_desc( column_id: ColumnId, connector_name: &str, additional_col_type: &str, @@ -87,9 +122,10 @@ pub fn build_additional_column_catalog( inner_field_name: Option<&str>, data_type: Option<&str>, reject_unknown_connector: bool, -) -> ConnectorResult { + is_cdc_backfill_table: bool, +) -> ConnectorResult { let compatible_columns = match ( - COMPATIBLE_ADDITIONAL_COLUMNS.get(connector_name), + get_supported_additional_columns(connector_name, is_cdc_backfill_table), reject_unknown_connector, ) { (Some(compat_cols), _) => compat_cols, @@ -118,71 +154,94 @@ pub fn build_additional_column_catalog( ) }); - let catalog = match additional_col_type { - "key" => ColumnCatalog { - column_desc: ColumnDesc::named_with_additional_column( - column_name, - column_id, - DataType::Bytea, - AdditionalColumn { - column_type: Some(AdditionalColumnType::Key(AdditionalColumnKey {})), - }, - ), - is_hidden: false, - }, - "timestamp" => ColumnCatalog { - column_desc: ColumnDesc::named_with_additional_column( - column_name, - column_id, - DataType::Timestamptz, - AdditionalColumn { - column_type: Some(AdditionalColumnType::Timestamp( - AdditionalColumnTimestamp {}, - )), - }, - ), - is_hidden: false, - }, - "partition" => ColumnCatalog { - column_desc: ColumnDesc::named_with_additional_column( - column_name, - column_id, - DataType::Varchar, - AdditionalColumn { - column_type: Some(AdditionalColumnType::Partition( - AdditionalColumnPartition {}, - )), - }, - ), - is_hidden: false, - }, - "offset" => ColumnCatalog { - column_desc: ColumnDesc::named_with_additional_column( - column_name, - column_id, - DataType::Varchar, - AdditionalColumn { - column_type: Some(AdditionalColumnType::Offset(AdditionalColumnOffset {})), - }, - ), - is_hidden: false, - }, - "file" => ColumnCatalog { - column_desc: ColumnDesc::named_with_additional_column( - column_name, - column_id, - DataType::Varchar, - AdditionalColumn { - column_type: Some(AdditionalColumnType::Filename(AdditionalColumnFilename {})), - }, - ), - is_hidden: false, - }, + let col_desc = match additional_col_type { + "key" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Bytea, + AdditionalColumn { + column_type: Some(AdditionalColumnType::Key(AdditionalColumnKey {})), + }, + ), + + "timestamp" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Timestamptz, + AdditionalColumn { + column_type: Some(AdditionalColumnType::Timestamp( + AdditionalColumnTimestamp {}, + )), + }, + ), + "partition" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Varchar, + AdditionalColumn { + column_type: Some(AdditionalColumnType::Partition( + AdditionalColumnPartition {}, + )), + }, + ), + "offset" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Varchar, + AdditionalColumn { + column_type: Some(AdditionalColumnType::Offset(AdditionalColumnOffset {})), + }, + ), + + "file" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Varchar, + AdditionalColumn { + column_type: Some(AdditionalColumnType::Filename(AdditionalColumnFilename {})), + }, + ), "header" => build_header_catalog(column_id, &column_name, inner_field_name, data_type), + "database_name" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Varchar, + AdditionalColumn { + column_type: Some(AdditionalColumnType::DatabaseName( + AdditionalDatabaseName {}, + )), + }, + ), + "schema_name" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Varchar, + AdditionalColumn { + column_type: Some(AdditionalColumnType::SchemaName(AdditionalSchemaName {})), + }, + ), + "table_name" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Varchar, + AdditionalColumn { + column_type: Some(AdditionalColumnType::TableName(AdditionalTableName {})), + }, + ), + "collection_name" => ColumnDesc::named_with_additional_column( + column_name, + column_id, + DataType::Varchar, + AdditionalColumn { + column_type: Some(AdditionalColumnType::CollectionName( + AdditionalCollectionName {}, + )), + }, + ), _ => unreachable!(), }; - Ok(catalog) + Ok(col_desc) } /// Utility function for adding partition and offset columns to the columns, if not specified by the user. @@ -190,16 +249,12 @@ pub fn build_additional_column_catalog( /// ## Returns /// - `columns_exist`: whether 1. `partition`/`file` and 2. `offset` columns are included in `columns`. /// - `additional_columns`: The `ColumnCatalog` for `partition`/`file` and `offset` columns. -pub fn add_partition_offset_cols( +pub fn source_add_partition_offset_cols( columns: &[ColumnCatalog], connector_name: &str, -) -> ([bool; 2], [ColumnCatalog; 2]) { +) -> ([bool; 2], [ColumnDesc; 2]) { let mut columns_exist = [false; 2]; - let mut last_column_id = columns - .iter() - .map(|c| c.column_desc.column_id) - .max() - .unwrap_or(ColumnId::placeholder()); + let mut last_column_id = max_column_id(columns); let additional_columns: Vec<_> = { let compat_col_types = COMPATIBLE_ADDITIONAL_COLUMNS @@ -211,7 +266,7 @@ pub fn add_partition_offset_cols( last_column_id = last_column_id.next(); if compat_col_types.contains(col_type) { Some( - build_additional_column_catalog( + build_additional_column_desc( last_column_id, connector_name, col_type, @@ -219,6 +274,7 @@ pub fn add_partition_offset_cols( None, None, false, + false, ) .unwrap(), ) @@ -231,13 +287,13 @@ pub fn add_partition_offset_cols( assert_eq!(additional_columns.len(), 2); use risingwave_pb::plan_common::additional_column::ColumnType; assert_matches::assert_matches!( - additional_columns[0].column_desc.additional_column, + additional_columns[0].additional_column, AdditionalColumn { column_type: Some(ColumnType::Partition(_) | ColumnType::Filename(_)), } ); assert_matches::assert_matches!( - additional_columns[1].column_desc.additional_column, + additional_columns[1].additional_column, AdditionalColumn { column_type: Some(ColumnType::Offset(_)), } @@ -268,7 +324,7 @@ fn build_header_catalog( col_name: &str, inner_field_name: Option<&str>, data_type: Option<&str>, -) -> ColumnCatalog { +) -> ColumnDesc { if let Some(inner) = inner_field_name { let (data_type, pb_data_type) = { if let Some(type_name) = data_type { @@ -299,32 +355,26 @@ fn build_header_catalog( ) } }; - ColumnCatalog { - column_desc: ColumnDesc::named_with_additional_column( - col_name, - column_id, - data_type, - AdditionalColumn { - column_type: Some(AdditionalColumnType::HeaderInner(AdditionalColumnHeader { - inner_field: inner.to_string(), - data_type: Some(pb_data_type), - })), - }, - ), - is_hidden: false, - } + ColumnDesc::named_with_additional_column( + col_name, + column_id, + data_type, + AdditionalColumn { + column_type: Some(AdditionalColumnType::HeaderInner(AdditionalColumnHeader { + inner_field: inner.to_string(), + data_type: Some(pb_data_type), + })), + }, + ) } else { - ColumnCatalog { - column_desc: ColumnDesc::named_with_additional_column( - col_name, - column_id, - DataType::List(get_kafka_header_item_datatype().into()), - AdditionalColumn { - column_type: Some(AdditionalColumnType::Headers(AdditionalColumnHeaders {})), - }, - ), - is_hidden: false, - } + ColumnDesc::named_with_additional_column( + col_name, + column_id, + DataType::List(get_kafka_header_item_datatype().into()), + AdditionalColumn { + column_type: Some(AdditionalColumnType::Headers(AdditionalColumnHeaders {})), + }, + ) } } diff --git a/src/connector/src/parser/avro/mod.rs b/src/connector/src/parser/avro/mod.rs index 1c22b326770c4..19193035bd567 100644 --- a/src/connector/src/parser/avro/mod.rs +++ b/src/connector/src/parser/avro/mod.rs @@ -13,7 +13,7 @@ // limitations under the License. mod parser; -pub mod schema_resolver; -pub mod util; +mod schema_resolver; -pub use parser::*; +pub use parser::{AvroAccessBuilder, AvroParserConfig}; +pub use schema_resolver::ConfluentSchemaCache; diff --git a/src/connector/src/parser/avro/parser.rs b/src/connector/src/parser/avro/parser.rs index 65601d9c3a3e6..5bd0038e3e839 100644 --- a/src/connector/src/parser/avro/parser.rs +++ b/src/connector/src/parser/avro/parser.rs @@ -19,15 +19,16 @@ use anyhow::Context; use apache_avro::types::Value; use apache_avro::{from_avro_datum, Reader, Schema}; use risingwave_common::{bail, try_match_expand}; +use risingwave_connector_codec::decoder::avro::{ + avro_schema_to_column_descs, AvroAccess, AvroParseOptions, ResolvedAvroSchema, +}; use risingwave_pb::plan_common::ColumnDesc; -use super::schema_resolver::ConfluentSchemaResolver; -use super::util::avro_schema_to_column_descs; +use super::ConfluentSchemaCache; use crate::error::ConnectorResult; -use crate::parser::unified::avro::{AvroAccess, AvroParseOptions}; use crate::parser::unified::AccessImpl; use crate::parser::util::bytes_from_url; -use crate::parser::{AccessBuilder, EncodingProperties, EncodingType}; +use crate::parser::{AccessBuilder, AvroProperties, EncodingProperties, EncodingType, MapHandling}; use crate::schema::schema_registry::{ extract_schema_id, get_subject_by_strategy, handle_sr_list, Client, }; @@ -35,17 +36,18 @@ use crate::schema::schema_registry::{ // Default avro access builder #[derive(Debug)] pub struct AvroAccessBuilder { - schema: Arc, - pub schema_resolver: Option>, + schema: Arc, + /// Refer to [`AvroParserConfig::writer_schema_cache`]. + pub writer_schema_cache: Option>, value: Option, } impl AccessBuilder for AvroAccessBuilder { - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { - self.value = self.parse_avro_value(&payload, Some(&*self.schema)).await?; + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + self.value = self.parse_avro_value(&payload).await?; Ok(AccessImpl::Avro(AvroAccess::new( self.value.as_ref().unwrap(), - AvroParseOptions::default().with_schema(&self.schema), + AvroParseOptions::create(&self.schema.resolved_schema), ))) } } @@ -55,7 +57,7 @@ impl AvroAccessBuilder { let AvroParserConfig { schema, key_schema, - schema_resolver, + writer_schema_cache, .. } = config; Ok(Self { @@ -63,162 +65,156 @@ impl AvroAccessBuilder { EncodingType::Key => key_schema.context("Avro with empty key schema")?, EncodingType::Value => schema, }, - schema_resolver, + writer_schema_cache, value: None, }) } - async fn parse_avro_value( - &self, - payload: &[u8], - reader_schema: Option<&Schema>, - ) -> ConnectorResult> { + /// Note: we should use unresolved schema to parsing bytes into avro value. + /// Otherwise it's an invalid schema and parsing will fail. (Avro error: Two named schema defined for same fullname) + /// + /// # Notes about how Avro data looks like + /// + /// First, it has two [serialization encodings: binary and JSON](https://avro.apache.org/docs/1.11.1/specification/#encodings). + /// They don't have magic bytes and cannot be distinguished on their own. + /// + /// But in different cases, it starts with different headers, or magic bytes, which can be confusing. + /// + /// ## `apache_avro` API and headers + /// + /// - `apache_avro::Reader`: [Object Container Files](https://avro.apache.org/docs/1.11.1/specification/#object-container-files): contains file header, starting with 4 bytes `Obj1`. This is a batch file encoding. We don't use it. + /// - `apache_avro::GenericSingleObjectReader`: [Single-object encoding](https://avro.apache.org/docs/1.11.1/specification/#single-object-encoding): starts with 2 bytes `0xC301`. This is designed to be used in places like Kafka, but Confluent schema registry doesn't use it. + /// - `apache_avro::from_avro_datum`: no header, binary encoding. This is what we should use. + /// + /// ## Confluent schema registry + /// + /// - In Kafka ([Confluent schema registry wire format](https://docs.confluent.io/platform/7.6/schema-registry/fundamentals/serdes-develop/index.html#wire-format)): + /// starts with 5 bytes`0x00{schema_id:08x}` followed by Avro binary encoding. + async fn parse_avro_value(&self, payload: &[u8]) -> ConnectorResult> { // parse payload to avro value // if use confluent schema, get writer schema from confluent schema registry - if let Some(resolver) = &self.schema_resolver { + if let Some(resolver) = &self.writer_schema_cache { let (schema_id, mut raw_payload) = extract_schema_id(payload)?; - let writer_schema = resolver.get(schema_id).await?; + let writer_schema = resolver.get_by_id(schema_id).await?; Ok(Some(from_avro_datum( writer_schema.as_ref(), &mut raw_payload, - reader_schema, + Some(&self.schema.original_schema), )?)) - } else if let Some(schema) = reader_schema { - let mut reader = Reader::with_schema(schema, payload)?; + } else { + // FIXME: we should not use `Reader` (file header) here. See comment above and https://github.com/risingwavelabs/risingwave/issues/12871 + let mut reader = Reader::with_schema(&self.schema.original_schema, payload)?; match reader.next() { Some(Ok(v)) => Ok(Some(v)), Some(Err(e)) => Err(e)?, None => bail!("avro parse unexpected eof"), } - } else { - unreachable!("both schema_resolver and reader_schema not exist"); } } } #[derive(Debug, Clone)] pub struct AvroParserConfig { - pub schema: Arc, - pub key_schema: Option>, - pub schema_resolver: Option>, + pub schema: Arc, + pub key_schema: Option>, + /// Writer schema is the schema used to write the data. When parsing Avro data, the exactly same schema + /// must be used to decode the message, and then convert it with the reader schema. + pub writer_schema_cache: Option>, + + pub map_handling: Option, } impl AvroParserConfig { pub async fn new(encoding_properties: EncodingProperties) -> ConnectorResult { - let avro_config = try_match_expand!(encoding_properties, EncodingProperties::Avro)?; - let schema_location = &avro_config.row_schema_location; - let enable_upsert = avro_config.enable_upsert; + let AvroProperties { + use_schema_registry, + row_schema_location: schema_location, + client_config, + aws_auth_props, + topic, + enable_upsert, + record_name, + key_record_name, + name_strategy, + map_handling, + } = try_match_expand!(encoding_properties, EncodingProperties::Avro)?; let url = handle_sr_list(schema_location.as_str())?; - if avro_config.use_schema_registry { - let client = Client::new(url, &avro_config.client_config)?; - let resolver = ConfluentSchemaResolver::new(client); + if use_schema_registry { + let client = Client::new(url, &client_config)?; + let resolver = ConfluentSchemaCache::new(client); let subject_key = if enable_upsert { Some(get_subject_by_strategy( - &avro_config.name_strategy, - avro_config.topic.as_str(), - avro_config.key_record_name.as_deref(), + &name_strategy, + topic.as_str(), + key_record_name.as_deref(), true, )?) } else { - if let Some(name) = &avro_config.key_record_name { - bail!("key.message = {name} not used"); + if let Some(name) = &key_record_name { + bail!("unused FORMAT ENCODE option: key.message='{name}'"); } None }; let subject_value = get_subject_by_strategy( - &avro_config.name_strategy, - avro_config.topic.as_str(), - avro_config.record_name.as_deref(), + &name_strategy, + topic.as_str(), + record_name.as_deref(), false, )?; tracing::debug!("infer key subject {subject_key:?}, value subject {subject_value}"); Ok(Self { - schema: resolver.get_by_subject_name(&subject_value).await?, + schema: Arc::new(ResolvedAvroSchema::create( + resolver.get_by_subject(&subject_value).await?, + )?), key_schema: if let Some(subject_key) = subject_key { - Some(resolver.get_by_subject_name(&subject_key).await?) + Some(Arc::new(ResolvedAvroSchema::create( + resolver.get_by_subject(&subject_key).await?, + )?)) } else { None }, - schema_resolver: Some(Arc::new(resolver)), + writer_schema_cache: Some(Arc::new(resolver)), + map_handling, }) } else { if enable_upsert { bail!("avro upsert without schema registry is not supported"); } let url = url.first().unwrap(); - let schema_content = bytes_from_url(url, avro_config.aws_auth_props.as_ref()).await?; + let schema_content = bytes_from_url(url, aws_auth_props.as_ref()).await?; let schema = Schema::parse_reader(&mut schema_content.as_slice()) .context("failed to parse avro schema")?; Ok(Self { - schema: Arc::new(schema), + schema: Arc::new(ResolvedAvroSchema::create(Arc::new(schema))?), key_schema: None, - schema_resolver: None, + writer_schema_cache: None, + map_handling, }) } } - pub fn extract_pks(&self) -> ConnectorResult> { - avro_schema_to_column_descs( - self.key_schema - .as_deref() - .ok_or_else(|| anyhow::format_err!("key schema is required"))?, - ) - } - pub fn map_to_columns(&self) -> ConnectorResult> { - avro_schema_to_column_descs(self.schema.as_ref()) + avro_schema_to_column_descs(&self.schema.resolved_schema, self.map_handling) + .map_err(Into::into) } } #[cfg(test)] mod test { - use std::collections::HashMap; use std::env; - use std::fs::OpenOptions; - use std::io::Write; - use std::ops::Sub; - use std::path::PathBuf; - use apache_avro::schema::RecordSchema; - use apache_avro::types::{Record, Value}; - use apache_avro::{Codec, Days, Duration, Millis, Months, Reader, Schema, Writer}; - use itertools::Itertools; - use risingwave_common::array::Op; - use risingwave_common::catalog::ColumnId; - use risingwave_common::row::Row; - use risingwave_common::types::{DataType, Date, Interval, ScalarImpl, Timestamptz}; - use risingwave_pb::catalog::StreamSourceInfo; - use risingwave_pb::plan_common::{PbEncodeType, PbFormatType}; use url::Url; use super::*; use crate::connector_common::AwsAuthProps; - use crate::error::ConnectorResult; - use crate::parser::plain_parser::PlainParser; - use crate::parser::unified::avro::unix_epoch_days; - use crate::parser::{ - AccessBuilderImpl, EncodingType, SourceStreamChunkBuilder, SpecificParserConfig, - }; - use crate::source::{SourceColumnDesc, SourceContext}; fn test_data_path(file_name: &str) -> String { let curr_dir = env::current_dir().unwrap().into_os_string(); curr_dir.into_string().unwrap() + "/src/test_data/" + file_name } - fn e2e_file_path(file_name: &str) -> String { - let curr_dir = env::current_dir().unwrap().into_os_string(); - let binding = PathBuf::from(curr_dir); - let dir = binding.parent().unwrap().parent().unwrap(); - dir.join("scripts/source/test_data/") - .join(file_name) - .to_str() - .unwrap() - .to_string() - } - #[tokio::test] #[ignore] async fn test_load_schema_from_s3() { @@ -255,322 +251,4 @@ mod test { assert!(schema.is_ok()); println!("schema = {:?}", schema.unwrap()); } - - async fn new_avro_conf_from_local(file_name: &str) -> ConnectorResult { - let schema_path = format!("file://{}", test_data_path(file_name)); - let info = StreamSourceInfo { - row_schema_location: schema_path.clone(), - use_schema_registry: false, - format: PbFormatType::Plain.into(), - row_encode: PbEncodeType::Avro.into(), - ..Default::default() - }; - let parser_config = SpecificParserConfig::new(&info, &HashMap::new())?; - AvroParserConfig::new(parser_config.encoding_config).await - } - - async fn new_avro_parser_from_local(file_name: &str) -> ConnectorResult { - let conf = new_avro_conf_from_local(file_name).await?; - - Ok(PlainParser { - key_builder: None, - payload_builder: AccessBuilderImpl::Avro(AvroAccessBuilder::new( - conf, - EncodingType::Value, - )?), - rw_columns: Vec::default(), - source_ctx: SourceContext::dummy().into(), - transaction_meta_builder: None, - }) - } - - #[tokio::test] - async fn test_avro_parser() { - let mut parser = new_avro_parser_from_local("simple-schema.avsc") - .await - .unwrap(); - let builder = try_match_expand!(&parser.payload_builder, AccessBuilderImpl::Avro).unwrap(); - let schema = builder.schema.clone(); - let record = build_avro_data(&schema); - assert_eq!(record.fields.len(), 11); - let mut writer = Writer::with_codec(&schema, Vec::new(), Codec::Snappy); - writer.append(record.clone()).unwrap(); - let flush = writer.flush().unwrap(); - assert!(flush > 0); - let input_data = writer.into_inner().unwrap(); - let columns = build_rw_columns(); - let mut builder = SourceStreamChunkBuilder::with_capacity(columns, 1); - { - let writer = builder.row_writer(); - parser - .parse_inner(None, Some(input_data), writer) - .await - .unwrap(); - } - let chunk = builder.finish(); - let (op, row) = chunk.rows().next().unwrap(); - assert_eq!(op, Op::Insert); - let row = row.into_owned_row(); - for (i, field) in record.fields.iter().enumerate() { - let value = field.clone().1; - match value { - Value::String(str) | Value::Union(_, box Value::String(str)) => { - assert_eq!(row[i], Some(ScalarImpl::Utf8(str.into_boxed_str()))); - } - Value::Boolean(bool_val) => { - assert_eq!(row[i], Some(ScalarImpl::Bool(bool_val))); - } - Value::Int(int_val) => { - assert_eq!(row[i], Some(ScalarImpl::Int32(int_val))); - } - Value::Long(i64_val) => { - assert_eq!(row[i], Some(ScalarImpl::Int64(i64_val))); - } - Value::Float(f32_val) => { - assert_eq!(row[i], Some(ScalarImpl::Float32(f32_val.into()))); - } - Value::Double(f64_val) => { - assert_eq!(row[i], Some(ScalarImpl::Float64(f64_val.into()))); - } - Value::Date(days) => { - assert_eq!( - row[i], - Some(ScalarImpl::Date( - Date::with_days(days + unix_epoch_days()).unwrap(), - )) - ); - } - Value::TimestampMillis(millis) => { - assert_eq!( - row[i], - Some(Timestamptz::from_millis(millis).unwrap().into()) - ); - } - Value::TimestampMicros(micros) => { - assert_eq!(row[i], Some(Timestamptz::from_micros(micros).into())); - } - Value::Bytes(bytes) => { - assert_eq!(row[i], Some(ScalarImpl::Bytea(bytes.into_boxed_slice()))); - } - Value::Duration(duration) => { - let months = u32::from(duration.months()) as i32; - let days = u32::from(duration.days()) as i32; - let usecs = (u32::from(duration.millis()) as i64) * 1000; // never overflows - assert_eq!( - row[i], - Some(Interval::from_month_day_usec(months, days, usecs).into()) - ); - } - _ => { - unreachable!() - } - } - } - } - - fn build_rw_columns() -> Vec { - vec![ - SourceColumnDesc::simple("id", DataType::Int32, ColumnId::from(0)), - SourceColumnDesc::simple("sequence_id", DataType::Int64, ColumnId::from(1)), - SourceColumnDesc::simple("name", DataType::Varchar, ColumnId::from(2)), - SourceColumnDesc::simple("score", DataType::Float32, ColumnId::from(3)), - SourceColumnDesc::simple("avg_score", DataType::Float64, ColumnId::from(4)), - SourceColumnDesc::simple("is_lasted", DataType::Boolean, ColumnId::from(5)), - SourceColumnDesc::simple("entrance_date", DataType::Date, ColumnId::from(6)), - SourceColumnDesc::simple("birthday", DataType::Timestamptz, ColumnId::from(7)), - SourceColumnDesc::simple("anniversary", DataType::Timestamptz, ColumnId::from(8)), - SourceColumnDesc::simple("passed", DataType::Interval, ColumnId::from(9)), - SourceColumnDesc::simple("bytes", DataType::Bytea, ColumnId::from(10)), - ] - } - - fn build_field(schema: &Schema) -> Option { - match schema { - Schema::String => Some(Value::String("str_value".to_string())), - Schema::Int => Some(Value::Int(32_i32)), - Schema::Long => Some(Value::Long(64_i64)), - Schema::Float => Some(Value::Float(32_f32)), - Schema::Double => Some(Value::Double(64_f64)), - Schema::Boolean => Some(Value::Boolean(true)), - Schema::Bytes => Some(Value::Bytes(vec![1, 2, 3, 4, 5])), - - Schema::Date => { - let original_date = Date::from_ymd_uncheck(1970, 1, 1).and_hms_uncheck(0, 0, 0); - let naive_date = Date::from_ymd_uncheck(1970, 1, 1).and_hms_uncheck(0, 0, 0); - let num_days = naive_date.0.sub(original_date.0).num_days() as i32; - Some(Value::Date(num_days)) - } - Schema::TimestampMillis => { - let datetime = Date::from_ymd_uncheck(1970, 1, 1).and_hms_uncheck(0, 0, 0); - let timestamp_mills = Value::TimestampMillis(datetime.0.timestamp() * 1_000); - Some(timestamp_mills) - } - Schema::TimestampMicros => { - let datetime = Date::from_ymd_uncheck(1970, 1, 1).and_hms_uncheck(0, 0, 0); - let timestamp_micros = Value::TimestampMicros(datetime.0.timestamp() * 1_000_000); - Some(timestamp_micros) - } - Schema::Duration => { - let months = Months::new(1); - let days = Days::new(1); - let millis = Millis::new(1000); - Some(Value::Duration(Duration::new(months, days, millis))) - } - - Schema::Union(union_schema) => { - let inner_schema = union_schema - .variants() - .iter() - .find_or_first(|s| !matches!(s, &&Schema::Null)) - .unwrap(); - - match build_field(inner_schema) { - None => { - let index_of_union = union_schema - .find_schema_with_known_schemata::<&Schema>(&Value::Null, None, &None) - .unwrap() - .0 as u32; - Some(Value::Union(index_of_union, Box::new(Value::Null))) - } - Some(value) => { - let index_of_union = union_schema - .find_schema_with_known_schemata::<&Schema>(&value, None, &None) - .unwrap() - .0 as u32; - Some(Value::Union(index_of_union, Box::new(value))) - } - } - } - _ => None, - } - } - - fn build_avro_data(schema: &Schema) -> Record<'_> { - let mut record = Record::new(schema).unwrap(); - if let Schema::Record(RecordSchema { - name: _, fields, .. - }) = schema.clone() - { - for field in &fields { - let value = build_field(&field.schema) - .unwrap_or_else(|| panic!("No value defined for field, {}", field.name)); - record.put(field.name.as_str(), value) - } - } - record - } - - #[tokio::test] - async fn test_map_to_columns() { - let conf = new_avro_conf_from_local("simple-schema.avsc") - .await - .unwrap(); - let columns = conf.map_to_columns().unwrap(); - assert_eq!(columns.len(), 11); - println!("{:?}", columns); - } - - #[tokio::test] - async fn test_new_avro_parser() { - let avro_parser_rs = new_avro_parser_from_local("simple-schema.avsc").await; - let avro_parser = avro_parser_rs.unwrap(); - println!("avro_parser = {:?}", avro_parser); - } - - #[tokio::test] - async fn test_avro_union_type() { - let parser = new_avro_parser_from_local("union-schema.avsc") - .await - .unwrap(); - let builder = try_match_expand!(&parser.payload_builder, AccessBuilderImpl::Avro).unwrap(); - let schema = &builder.schema; - let mut null_record = Record::new(schema).unwrap(); - null_record.put("id", Value::Int(5)); - null_record.put("age", Value::Union(0, Box::new(Value::Null))); - null_record.put("sequence_id", Value::Union(0, Box::new(Value::Null))); - null_record.put("name", Value::Union(0, Box::new(Value::Null))); - null_record.put("score", Value::Union(1, Box::new(Value::Null))); - null_record.put("avg_score", Value::Union(0, Box::new(Value::Null))); - null_record.put("is_lasted", Value::Union(0, Box::new(Value::Null))); - null_record.put("entrance_date", Value::Union(0, Box::new(Value::Null))); - null_record.put("birthday", Value::Union(0, Box::new(Value::Null))); - null_record.put("anniversary", Value::Union(0, Box::new(Value::Null))); - - let mut writer = Writer::new(schema, Vec::new()); - writer.append(null_record).unwrap(); - writer.flush().unwrap(); - - let record = build_avro_data(schema); - writer.append(record).unwrap(); - writer.flush().unwrap(); - - let records = writer.into_inner().unwrap(); - - let reader: Vec<_> = Reader::with_schema(schema, &records[..]).unwrap().collect(); - assert_eq!(2, reader.len()); - let null_record_expected: Vec<(String, Value)> = vec![ - ("id".to_string(), Value::Int(5)), - ("age".to_string(), Value::Union(0, Box::new(Value::Null))), - ( - "sequence_id".to_string(), - Value::Union(0, Box::new(Value::Null)), - ), - ("name".to_string(), Value::Union(0, Box::new(Value::Null))), - ("score".to_string(), Value::Union(1, Box::new(Value::Null))), - ( - "avg_score".to_string(), - Value::Union(0, Box::new(Value::Null)), - ), - ( - "is_lasted".to_string(), - Value::Union(0, Box::new(Value::Null)), - ), - ( - "entrance_date".to_string(), - Value::Union(0, Box::new(Value::Null)), - ), - ( - "birthday".to_string(), - Value::Union(0, Box::new(Value::Null)), - ), - ( - "anniversary".to_string(), - Value::Union(0, Box::new(Value::Null)), - ), - ]; - let null_record_value = reader.first().unwrap().as_ref().unwrap(); - match null_record_value { - Value::Record(values) => { - assert_eq!(values, &null_record_expected) - } - _ => unreachable!(), - } - } - - // run this script when updating `simple-schema.avsc`, the script will generate new value in - // `avro_bin.1` - #[ignore] - #[tokio::test] - async fn update_avro_payload() { - let conf = new_avro_conf_from_local("simple-schema.avsc") - .await - .unwrap(); - let mut writer = Writer::new(&conf.schema, Vec::new()); - let record = build_avro_data(&conf.schema); - writer.append(record).unwrap(); - let encoded = writer.into_inner().unwrap(); - println!("path = {:?}", e2e_file_path("avro_bin.1")); - let mut file = OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(e2e_file_path("avro_bin.1")) - .unwrap(); - file.write_all(encoded.as_slice()).unwrap(); - println!( - "encoded = {:?}", - String::from_utf8_lossy(encoded.as_slice()) - ); - } } diff --git a/src/connector/src/parser/avro/schema_resolver.rs b/src/connector/src/parser/avro/schema_resolver.rs index cdc52de7accee..058f9bcbf7ea3 100644 --- a/src/connector/src/parser/avro/schema_resolver.rs +++ b/src/connector/src/parser/avro/schema_resolver.rs @@ -21,13 +21,20 @@ use moka::future::Cache; use crate::error::ConnectorResult; use crate::schema::schema_registry::{Client, ConfluentSchema}; +/// Fetch schemas from confluent schema registry and cache them. +/// +/// Background: This is mainly used for Avro **writer schema** (during schema evolution): When decoding an Avro message, +/// we must get the message's schema id, and use the *exactly same schema* to decode the message, and then +/// convert it with the reader schema. (This is also why Avro has to be used with a schema registry instead of a static schema file.) +/// +/// TODO: support protobuf (not sure if it's needed) #[derive(Debug)] -pub struct ConfluentSchemaResolver { +pub struct ConfluentSchemaCache { writer_schemas: Cache>, confluent_client: Client, } -impl ConfluentSchemaResolver { +impl ConfluentSchemaCache { async fn parse_and_cache_schema( &self, raw_schema: ConfluentSchema, @@ -43,29 +50,23 @@ impl ConfluentSchemaResolver { /// Create a new `ConfluentSchemaResolver` pub fn new(client: Client) -> Self { - ConfluentSchemaResolver { + ConfluentSchemaCache { writer_schemas: Cache::new(u64::MAX), confluent_client: client, } } - pub async fn get_by_subject_name(&self, subject_name: &str) -> ConnectorResult> { - let raw_schema = self.get_raw_schema_by_subject_name(subject_name).await?; - self.parse_and_cache_schema(raw_schema).await - } - - pub async fn get_raw_schema_by_subject_name( - &self, - subject_name: &str, - ) -> ConnectorResult { - self.confluent_client + /// Gets the latest schema by subject name, which is used as *reader schema*. + pub async fn get_by_subject(&self, subject_name: &str) -> ConnectorResult> { + let raw_schema = self + .confluent_client .get_schema_by_subject(subject_name) - .await - .map_err(Into::into) + .await?; + self.parse_and_cache_schema(raw_schema).await } - // get the writer schema by id - pub async fn get(&self, schema_id: i32) -> ConnectorResult> { + /// Gets the a specific schema by id, which is used as *writer schema*. + pub async fn get_by_id(&self, schema_id: i32) -> ConnectorResult> { // TODO: use `get_with` if let Some(schema) = self.writer_schemas.get(&schema_id).await { Ok(schema) diff --git a/src/connector/src/parser/avro/util.rs b/src/connector/src/parser/avro/util.rs deleted file mode 100644 index a58ad884fd886..0000000000000 --- a/src/connector/src/parser/avro/util.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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::LazyLock; - -use apache_avro::schema::{DecimalSchema, RecordSchema, Schema}; -use itertools::Itertools; -use risingwave_common::bail; -use risingwave_common::log::LogSuppresser; -use risingwave_common::types::{DataType, Decimal}; -use risingwave_pb::plan_common::{AdditionalColumn, ColumnDesc, ColumnDescVersion}; - -use crate::error::ConnectorResult; - -pub fn avro_schema_to_column_descs(schema: &Schema) -> ConnectorResult> { - if let Schema::Record(RecordSchema { fields, .. }) = schema { - let mut index = 0; - let fields = fields - .iter() - .map(|field| avro_field_to_column_desc(&field.name, &field.schema, &mut index)) - .collect::>>()?; - Ok(fields) - } else { - bail!("schema invalid, record type required at top level of the schema."); - } -} - -const DBZ_VARIABLE_SCALE_DECIMAL_NAME: &str = "VariableScaleDecimal"; -const DBZ_VARIABLE_SCALE_DECIMAL_NAMESPACE: &str = "io.debezium.data"; - -fn avro_field_to_column_desc( - name: &str, - schema: &Schema, - index: &mut i32, -) -> ConnectorResult { - let data_type = avro_type_mapping(schema)?; - match schema { - Schema::Record(RecordSchema { - name: schema_name, - fields, - .. - }) => { - let vec_column = fields - .iter() - .map(|f| avro_field_to_column_desc(&f.name, &f.schema, index)) - .collect::>>()?; - *index += 1; - Ok(ColumnDesc { - column_type: Some(data_type.to_protobuf()), - column_id: *index, - name: name.to_owned(), - field_descs: vec_column, - type_name: schema_name.to_string(), - generated_or_default_column: None, - description: None, - additional_column_type: 0, // deprecated - additional_column: Some(AdditionalColumn { column_type: None }), - version: ColumnDescVersion::Pr13707 as i32, - }) - } - _ => { - *index += 1; - Ok(ColumnDesc { - column_type: Some(data_type.to_protobuf()), - column_id: *index, - name: name.to_owned(), - additional_column: Some(AdditionalColumn { column_type: None }), - version: ColumnDescVersion::Pr13707 as i32, - ..Default::default() - }) - } - } -} - -fn avro_type_mapping(schema: &Schema) -> ConnectorResult { - let data_type = match schema { - Schema::String => DataType::Varchar, - Schema::Int => DataType::Int32, - Schema::Long => DataType::Int64, - Schema::Boolean => DataType::Boolean, - Schema::Float => DataType::Float32, - Schema::Double => DataType::Float64, - Schema::Decimal(DecimalSchema { precision, .. }) => { - if *precision > Decimal::MAX_PRECISION.into() { - static LOG_SUPPERSSER: LazyLock = - LazyLock::new(LogSuppresser::default); - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::warn!( - suppressed_count, - "RisingWave supports decimal precision up to {}, but got {}. Will truncate.", - Decimal::MAX_PRECISION, - precision - ); - } - } - DataType::Decimal - } - Schema::Date => DataType::Date, - Schema::LocalTimestampMillis => DataType::Timestamp, - Schema::LocalTimestampMicros => DataType::Timestamp, - Schema::TimestampMillis => DataType::Timestamptz, - Schema::TimestampMicros => DataType::Timestamptz, - Schema::Duration => DataType::Interval, - Schema::Bytes => DataType::Bytea, - Schema::Enum { .. } => DataType::Varchar, - Schema::TimeMillis => DataType::Time, - Schema::TimeMicros => DataType::Time, - Schema::Record(RecordSchema { fields, name, .. }) => { - if name.name == DBZ_VARIABLE_SCALE_DECIMAL_NAME - && name.namespace == Some(DBZ_VARIABLE_SCALE_DECIMAL_NAMESPACE.into()) - { - return Ok(DataType::Decimal); - } - - let struct_fields = fields - .iter() - .map(|f| avro_type_mapping(&f.schema)) - .collect::>>()?; - let struct_names = fields.iter().map(|f| f.name.clone()).collect_vec(); - DataType::new_struct(struct_fields, struct_names) - } - Schema::Array(item_schema) => { - let item_type = avro_type_mapping(item_schema.as_ref())?; - DataType::List(Box::new(item_type)) - } - Schema::Union(union_schema) => { - let nested_schema = union_schema - .variants() - .iter() - .find_or_first(|s| !matches!(s, Schema::Null)) - .ok_or_else(|| { - anyhow::format_err!("unsupported type in Avro: {:?}", union_schema) - })?; - - avro_type_mapping(nested_schema)? - } - Schema::Ref { name } => { - if name.name == DBZ_VARIABLE_SCALE_DECIMAL_NAME - && name.namespace == Some(DBZ_VARIABLE_SCALE_DECIMAL_NAMESPACE.into()) - { - DataType::Decimal - } else { - bail!("unsupported type in Avro: {:?}", schema); - } - } - _ => bail!("unsupported type in Avro: {:?}", schema), - }; - - Ok(data_type) -} diff --git a/src/connector/src/parser/bytes_parser.rs b/src/connector/src/parser/bytes_parser.rs index 255c3ef829c79..eeccf17be7a27 100644 --- a/src/connector/src/parser/bytes_parser.rs +++ b/src/connector/src/parser/bytes_parser.rs @@ -26,7 +26,7 @@ pub struct BytesAccessBuilder { impl AccessBuilder for BytesAccessBuilder { #[allow(clippy::unused_async)] - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { Ok(AccessImpl::Bytes(BytesAccess::new( &self.column_name, payload, @@ -63,7 +63,6 @@ mod tests { async fn test_bytes_parser(get_payload: fn() -> Vec>) { let descs = vec![SourceColumnDesc::simple("id", DataType::Bytea, 0.into())]; let props = SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Bytes(BytesProperties { column_name: None }), protocol_config: ProtocolProperties::Plain, }; diff --git a/src/connector/src/parser/canal/simd_json_parser.rs b/src/connector/src/parser/canal/simd_json_parser.rs index 9bc236392eb48..ac8a63a6457e7 100644 --- a/src/connector/src/parser/canal/simd_json_parser.rs +++ b/src/connector/src/parser/canal/simd_json_parser.rs @@ -141,7 +141,6 @@ mod tests { use super::*; use crate::parser::SourceStreamChunkBuilder; - use crate::source::SourceColumnDesc; #[tokio::test] async fn test_data_types() { diff --git a/src/connector/src/parser/common.rs b/src/connector/src/parser/common.rs index 71ce50b29793a..102014966db42 100644 --- a/src/connector/src/parser/common.rs +++ b/src/connector/src/parser/common.rs @@ -18,10 +18,10 @@ use simd_json::BorrowedValue; /// Get a value from a json object by key, case insensitive. /// /// Returns `None` if the given json value is not an object, or the key is not found. -pub(crate) fn json_object_get_case_insensitive<'a, 'b>( - v: &'b simd_json::BorrowedValue<'a>, - key: &'b str, -) -> Option<&'b BorrowedValue<'a>> { +pub(crate) fn json_object_get_case_insensitive<'b>( + v: &'b simd_json::BorrowedValue<'b>, + key: &str, +) -> Option<&'b BorrowedValue<'b>> { let obj = v.as_object()?; let value = obj.get(key); if value.is_some() { diff --git a/src/connector/src/parser/csv_parser.rs b/src/connector/src/parser/csv_parser.rs index eaf09ab839891..386ad392d96ba 100644 --- a/src/connector/src/parser/csv_parser.rs +++ b/src/connector/src/parser/csv_parser.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use risingwave_common::types::{Date, Decimal, Time, Timestamp, Timestamptz}; +use risingwave_common::cast::str_to_bool; +use risingwave_common::types::{Date, Decimal, ScalarImpl, Time, Timestamp, Timestamptz}; use super::unified::{AccessError, AccessResult}; use super::{ByteStreamSourceParser, CsvProperties}; @@ -76,7 +77,15 @@ impl CsvParser { fn parse_string(dtype: &DataType, v: String) -> AccessResult { let v = match dtype { // mysql use tinyint to represent boolean - DataType::Boolean => (parse!(v, i16)? != 0).into(), + DataType::Boolean => { + str_to_bool(&v) + .map(ScalarImpl::Bool) + .map_err(|_| AccessError::TypeError { + expected: "boolean".to_owned(), + got: "string".to_owned(), + value: v, + })? + } DataType::Int16 => parse!(v, i16)?.into(), DataType::Int32 => parse!(v, i32)?.into(), DataType::Int64 => parse!(v, i64)?.into(), @@ -112,7 +121,7 @@ impl CsvParser { // The header row does not output a row, so we return early. return Ok(()); } - writer.insert(|desc| { + writer.do_insert(|desc| { if let Some(i) = headers.iter().position(|name| name == &desc.name) { let value = fields.get_mut(i).map(std::mem::take).unwrap_or_default(); if value.is_empty() { @@ -125,7 +134,7 @@ impl CsvParser { })?; } else { fields.reverse(); - writer.insert(|desc| { + writer.do_insert(|desc| { if let Some(value) = fields.pop() { if value.is_empty() { return Ok(None); @@ -168,7 +177,7 @@ impl ByteStreamSourceParser for CsvParser { mod tests { use risingwave_common::array::Op; use risingwave_common::row::Row; - use risingwave_common::types::{DataType, ScalarImpl, ToOwnedDatum}; + use risingwave_common::types::{DataType, ToOwnedDatum}; use super::*; use crate::parser::SourceStreamChunkBuilder; @@ -377,4 +386,63 @@ mod tests { ); } } + + #[test] + fn test_parse_boolean() { + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "1".to_string()).unwrap(), + Some(true.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "t".to_string()).unwrap(), + Some(true.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "T".to_string()).unwrap(), + Some(true.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "true".to_string()).unwrap(), + Some(true.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "TRUE".to_string()).unwrap(), + Some(true.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "True".to_string()).unwrap(), + Some(true.into()) + ); + + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "0".to_string()).unwrap(), + Some(false.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "f".to_string()).unwrap(), + Some(false.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "F".to_string()).unwrap(), + Some(false.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "false".to_string()).unwrap(), + Some(false.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "FALSE".to_string()).unwrap(), + Some(false.into()) + ); + assert_eq!( + CsvParser::parse_string(&DataType::Boolean, "False".to_string()).unwrap(), + Some(false.into()) + ); + + assert!(CsvParser::parse_string(&DataType::Boolean, "2".to_string()).is_err()); + assert!(CsvParser::parse_string(&DataType::Boolean, "t1".to_string()).is_err()); + assert!(CsvParser::parse_string(&DataType::Boolean, "f1".to_string()).is_err()); + assert!(CsvParser::parse_string(&DataType::Boolean, "false1".to_string()).is_err()); + assert!(CsvParser::parse_string(&DataType::Boolean, "TRUE1".to_string()).is_err()); + } } diff --git a/src/connector/src/parser/debezium/avro_parser.rs b/src/connector/src/parser/debezium/avro_parser.rs index 29d9139d221cf..1a40b87c9d498 100644 --- a/src/connector/src/parser/debezium/avro_parser.rs +++ b/src/connector/src/parser/debezium/avro_parser.rs @@ -18,30 +18,25 @@ use std::sync::Arc; use apache_avro::types::Value; use apache_avro::{from_avro_datum, Schema}; use risingwave_common::try_match_expand; +use risingwave_connector_codec::decoder::avro::{ + avro_extract_field_schema, avro_schema_skip_union, avro_schema_to_column_descs, AvroAccess, + AvroParseOptions, ResolvedAvroSchema, +}; use risingwave_pb::catalog::PbSchemaRegistryNameStrategy; use risingwave_pb::plan_common::ColumnDesc; use crate::error::ConnectorResult; -use crate::parser::avro::schema_resolver::ConfluentSchemaResolver; -use crate::parser::avro::util::avro_schema_to_column_descs; -use crate::parser::unified::avro::{ - avro_extract_field_schema, avro_schema_skip_union, AvroAccess, AvroParseOptions, -}; +use crate::parser::avro::ConfluentSchemaCache; use crate::parser::unified::AccessImpl; use crate::parser::{AccessBuilder, EncodingProperties, EncodingType}; use crate::schema::schema_registry::{ extract_schema_id, get_subject_by_strategy, handle_sr_list, Client, }; -const BEFORE: &str = "before"; -const AFTER: &str = "after"; -const OP: &str = "op"; -const PAYLOAD: &str = "payload"; - #[derive(Debug)] pub struct DebeziumAvroAccessBuilder { - schema: Schema, - schema_resolver: Arc, + schema: ResolvedAvroSchema, + schema_resolver: Arc, key_schema: Option>, value: Option, encoding_type: EncodingType, @@ -49,9 +44,9 @@ pub struct DebeziumAvroAccessBuilder { // TODO: reduce encodingtype match impl AccessBuilder for DebeziumAvroAccessBuilder { - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { let (schema_id, mut raw_payload) = extract_schema_id(&payload)?; - let schema = self.schema_resolver.get(schema_id).await?; + let schema = self.schema_resolver.get_by_id(schema_id).await?; self.value = Some(from_avro_datum(schema.as_ref(), &mut raw_payload, None)?); self.key_schema = match self.encoding_type { EncodingType::Key => Some(schema), @@ -59,9 +54,10 @@ impl AccessBuilder for DebeziumAvroAccessBuilder { }; Ok(AccessImpl::Avro(AvroAccess::new( self.value.as_mut().unwrap(), - AvroParseOptions::default().with_schema(match self.encoding_type { + // Assumption: Key will not contain reference, so unresolved schema can work here. + AvroParseOptions::create(match self.encoding_type { EncodingType::Key => self.key_schema.as_mut().unwrap(), - EncodingType::Value => &self.schema, + EncodingType::Value => &self.schema.resolved_schema, }), ))) } @@ -78,11 +74,8 @@ impl DebeziumAvroAccessBuilder { .. } = config; - let resolver = apache_avro::schema::ResolvedSchema::try_from(&*outer_schema)?; - // todo: to_resolved may cause stackoverflow if there's a loop in the schema - let schema = resolver.to_resolved(&outer_schema)?; Ok(Self { - schema, + schema: ResolvedAvroSchema::create(outer_schema)?, schema_resolver, key_schema: None, value: None, @@ -96,7 +89,7 @@ impl DebeziumAvroAccessBuilder { pub struct DebeziumAvroParserConfig { pub key_schema: Arc, pub outer_schema: Arc, - pub schema_resolver: Arc, + pub schema_resolver: Arc, } impl DebeziumAvroParserConfig { @@ -107,13 +100,13 @@ impl DebeziumAvroParserConfig { let kafka_topic = &avro_config.topic; let url = handle_sr_list(schema_location)?; let client = Client::new(url, client_config)?; - let resolver = ConfluentSchemaResolver::new(client); + let resolver = ConfluentSchemaCache::new(client); let name_strategy = &PbSchemaRegistryNameStrategy::Unspecified; let key_subject = get_subject_by_strategy(name_strategy, kafka_topic, None, true)?; let val_subject = get_subject_by_strategy(name_strategy, kafka_topic, None, false)?; - let key_schema = resolver.get_by_subject_name(&key_subject).await?; - let outer_schema = resolver.get_by_subject_name(&val_subject).await?; + let key_schema = resolver.get_by_subject(&key_subject).await?; + let outer_schema = resolver.get_by_subject(&val_subject).await?; Ok(Self { key_schema, @@ -123,14 +116,27 @@ impl DebeziumAvroParserConfig { } pub fn extract_pks(&self) -> ConnectorResult> { - avro_schema_to_column_descs(&self.key_schema) + avro_schema_to_column_descs( + &self.key_schema, + // TODO: do we need to support map type here? + None, + ) + .map_err(Into::into) } pub fn map_to_columns(&self) -> ConnectorResult> { - avro_schema_to_column_descs(avro_schema_skip_union(avro_extract_field_schema( - &self.outer_schema, - Some("before"), - )?)?) + avro_schema_to_column_descs( + avro_schema_skip_union(avro_extract_field_schema( + // FIXME: use resolved schema here. + // Currently it works because "after" refers to a subtree in "before", + // but in theory, inside "before" there could also be a reference. + &self.outer_schema, + Some("before"), + )?)?, + // TODO: do we need to support map type here? + None, + ) + .map_err(Into::into) } } @@ -139,9 +145,8 @@ mod tests { use std::io::Read; use std::path::PathBuf; - use apache_avro::Schema; use itertools::Itertools; - use maplit::{convert_args, hashmap}; + use maplit::{btreemap, convert_args}; use risingwave_common::array::Op; use risingwave_common::catalog::ColumnDesc as CatColumnDesc; use risingwave_common::row::{OwnedRow, Row}; @@ -151,9 +156,7 @@ mod tests { use risingwave_pb::plan_common::{PbEncodeType, PbFormatType}; use super::*; - use crate::parser::{ - DebeziumAvroParserConfig, DebeziumParser, SourceStreamChunkBuilder, SpecificParserConfig, - }; + use crate::parser::{DebeziumParser, SourceStreamChunkBuilder, SpecificParserConfig}; use crate::source::{SourceColumnDesc, SourceContext}; const DEBEZIUM_AVRO_DATA: &[u8] = b"\x00\x00\x00\x00\x06\x00\x02\xd2\x0f\x0a\x53\x61\x6c\x6c\x79\x0c\x54\x68\x6f\x6d\x61\x73\x2a\x73\x61\x6c\x6c\x79\x2e\x74\x68\x6f\x6d\x61\x73\x40\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x16\x32\x2e\x31\x2e\x32\x2e\x46\x69\x6e\x61\x6c\x0a\x6d\x79\x73\x71\x6c\x12\x64\x62\x73\x65\x72\x76\x65\x72\x31\xc0\xb4\xe8\xb7\xc9\x61\x00\x30\x66\x69\x72\x73\x74\x5f\x69\x6e\x5f\x64\x61\x74\x61\x5f\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x12\x69\x6e\x76\x65\x6e\x74\x6f\x72\x79\x00\x02\x12\x63\x75\x73\x74\x6f\x6d\x65\x72\x73\x00\x00\x20\x6d\x79\x73\x71\x6c\x2d\x62\x69\x6e\x2e\x30\x30\x30\x30\x30\x33\x8c\x06\x00\x00\x00\x02\x72\x02\x92\xc3\xe8\xb7\xc9\x61\x00"; @@ -245,7 +248,7 @@ mod tests { } "#; let key_schema = Schema::parse_str(key_schema_str).unwrap(); - let names: Vec = avro_schema_to_column_descs(&key_schema) + let names: Vec = avro_schema_to_column_descs(&key_schema, None) .unwrap() .drain(..) .map(|d| d.name) @@ -301,7 +304,7 @@ mod tests { } "#; let schema = Schema::parse_str(test_schema_str).unwrap(); - let columns = avro_schema_to_column_descs(&schema).unwrap(); + let columns = avro_schema_to_column_descs(&schema, None).unwrap(); for col in &columns { let dtype = col.column_type.as_ref().unwrap(); println!("name = {}, type = {:?}", col.name, dtype.type_name); @@ -319,6 +322,7 @@ mod tests { avro_extract_field_schema(&outer_schema, Some("before")).unwrap(), ) .unwrap(), + None, ) .unwrap() .into_iter() @@ -350,7 +354,7 @@ mod tests { #[ignore] #[tokio::test] async fn test_debezium_avro_parser() -> crate::error::ConnectorResult<()> { - let props = convert_args!(hashmap!( + let props = convert_args!(btreemap!( "kafka.topic" => "dbserver1.inventory.customers" )); let info = StreamSourceInfo { diff --git a/src/connector/src/parser/debezium/debezium_parser.rs b/src/connector/src/parser/debezium/debezium_parser.rs index 52d1e4e4a15a2..bc43e556cb4f6 100644 --- a/src/connector/src/parser/debezium/debezium_parser.rs +++ b/src/connector/src/parser/debezium/debezium_parser.rs @@ -19,7 +19,6 @@ use risingwave_common::bail; use super::simd_json_parser::DebeziumJsonAccessBuilder; use super::{DebeziumAvroAccessBuilder, DebeziumAvroParserConfig}; use crate::error::ConnectorResult; -use crate::extract_key_config; use crate::parser::unified::debezium::DebeziumChangeEvent; use crate::parser::unified::json::TimestamptzHandling; use crate::parser::unified::util::apply_row_operation_on_stream_chunk_writer; @@ -89,8 +88,8 @@ impl DebeziumParser { rw_columns: Vec, source_ctx: SourceContextRef, ) -> ConnectorResult { - let (key_config, key_type) = extract_key_config!(props); - let key_builder = build_accessor_builder(key_config, key_type).await?; + let key_builder = + build_accessor_builder(props.encoding_config.clone(), EncodingType::Key).await?; let payload_builder = build_accessor_builder(props.encoding_config, EncodingType::Value).await?; let debezium_props = if let ProtocolProperties::Debezium(props) = props.protocol_config { @@ -114,7 +113,6 @@ impl DebeziumParser { use crate::parser::JsonProperties; let props = SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Json(JsonProperties { use_schema_registry: false, timestamptz_handling: None, @@ -198,6 +196,11 @@ mod tests { use std::sync::Arc; use risingwave_common::catalog::{ColumnCatalog, ColumnDesc, ColumnId}; + use risingwave_common::row::Row; + use risingwave_common::types::Timestamptz; + use risingwave_pb::plan_common::{ + additional_column, AdditionalColumn, AdditionalColumnTimestamp, + }; use super::*; use crate::parser::{JsonProperties, SourceStreamChunkBuilder, TransactionControl}; @@ -220,7 +223,6 @@ mod tests { .collect::>(); let props = SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Json(JsonProperties { use_schema_registry: false, timestamptz_handling: None, @@ -266,4 +268,66 @@ mod tests { _ => panic!("unexpected parse result: {:?}", res), } } + + #[tokio::test] + async fn test_parse_additional_columns() { + let columns = vec![ + ColumnDesc::named("O_ORDERKEY", ColumnId::new(1), DataType::Int64), + ColumnDesc::named("O_CUSTKEY", ColumnId::new(2), DataType::Int64), + ColumnDesc::named("O_ORDERSTATUS", ColumnId::new(3), DataType::Varchar), + ColumnDesc::named("O_TOTALPRICE", ColumnId::new(4), DataType::Decimal), + ColumnDesc::named("O_ORDERDATE", ColumnId::new(5), DataType::Date), + ColumnDesc::named_with_additional_column( + "commit_ts", + ColumnId::new(6), + DataType::Timestamptz, + AdditionalColumn { + column_type: Some(additional_column::ColumnType::Timestamp( + AdditionalColumnTimestamp {}, + )), + }, + ), + ]; + + let columns = columns + .iter() + .map(SourceColumnDesc::from) + .collect::>(); + + let props = SpecificParserConfig { + encoding_config: EncodingProperties::Json(JsonProperties { + use_schema_registry: false, + timestamptz_handling: None, + }), + protocol_config: ProtocolProperties::Debezium(DebeziumProps::default()), + }; + let source_ctx = SourceContext { + connector_props: ConnectorProperties::PostgresCdc(Box::default()), + ..SourceContext::dummy() + }; + let mut parser = DebeziumParser::new(props, columns.clone(), Arc::new(source_ctx)) + .await + .unwrap(); + let mut builder = SourceStreamChunkBuilder::with_capacity(columns, 0); + + let payload = r#"{ "payload": { "before": null, "after": { "O_ORDERKEY": 5, "O_CUSTKEY": 44485, "O_ORDERSTATUS": "F", "O_TOTALPRICE": "144659.20", "O_ORDERDATE": "1994-07-30" }, "source": { "version": "1.9.7.Final", "connector": "mysql", "name": "RW_CDC_1002", "ts_ms": 1695277757000, "snapshot": "last", "db": "mydb", "sequence": null, "table": "orders_new", "server_id": 0, "gtid": null, "file": "binlog.000008", "pos": 3693, "row": 0, "thread": null, "query": null }, "op": "c", "ts_ms": 1695277757017, "transaction": null } }"#; + + let res = parser + .parse_one_with_txn( + None, + Some(payload.as_bytes().to_vec()), + builder.row_writer(), + ) + .await; + match res { + Ok(ParseResult::Rows) => { + let chunk = builder.finish(); + for (_, row) in chunk.rows() { + let commit_ts = row.datum_at(5).unwrap().into_timestamptz(); + assert_eq!(commit_ts, Timestamptz::from_millis(1695277757000).unwrap()); + } + } + _ => panic!("unexpected parse result: {:?}", res), + } + } } diff --git a/src/connector/src/parser/debezium/mod.rs b/src/connector/src/parser/debezium/mod.rs index 2150af053ff2b..5b5416e647268 100644 --- a/src/connector/src/parser/debezium/mod.rs +++ b/src/connector/src/parser/debezium/mod.rs @@ -17,7 +17,6 @@ mod avro_parser; mod debezium_parser; mod mongo_json_parser; -mod operators; pub mod simd_json_parser; pub use avro_parser::*; pub use debezium_parser::*; diff --git a/src/connector/src/parser/debezium/mongo_json_parser.rs b/src/connector/src/parser/debezium/mongo_json_parser.rs index 2de46a9aa5460..4e3a679c31816 100644 --- a/src/connector/src/parser/debezium/mongo_json_parser.rs +++ b/src/connector/src/parser/debezium/mongo_json_parser.rs @@ -23,7 +23,7 @@ use crate::parser::simd_json_parser::DebeziumMongoJsonAccessBuilder; use crate::parser::unified::debezium::DebeziumChangeEvent; use crate::parser::unified::util::apply_row_operation_on_stream_chunk_writer; use crate::parser::{ - AccessBuilderImpl, ByteStreamSourceParser, EncodingProperties, JsonProperties, ParserFormat, + AccessBuilderImpl, ByteStreamSourceParser, EncodingProperties, ParserFormat, SourceStreamChunkRowWriter, }; use crate::source::{SourceColumnDesc, SourceContext, SourceContextRef}; @@ -31,8 +31,6 @@ use crate::source::{SourceColumnDesc, SourceContext, SourceContextRef}; #[derive(Debug)] pub struct DebeziumMongoJsonParser { pub(crate) rw_columns: Vec, - id_column: SourceColumnDesc, - payload_column: SourceColumnDesc, source_ctx: SourceContextRef, key_builder: AccessBuilderImpl, payload_builder: AccessBuilderImpl, @@ -40,7 +38,7 @@ pub struct DebeziumMongoJsonParser { fn build_accessor_builder(config: EncodingProperties) -> anyhow::Result { match config { - EncodingProperties::MongoJson(_) => Ok(AccessBuilderImpl::DebeziumMongoJson( + EncodingProperties::MongoJson => Ok(AccessBuilderImpl::DebeziumMongoJson( DebeziumMongoJsonAccessBuilder::new()?, )), _ => bail!("unsupported encoding for DEBEZIUM_MONGO format"), @@ -52,7 +50,7 @@ impl DebeziumMongoJsonParser { rw_columns: Vec, source_ctx: SourceContextRef, ) -> ConnectorResult { - let id_column = rw_columns + let _id_column = rw_columns .iter() .find(|desc| { desc.name == "_id" @@ -65,27 +63,28 @@ impl DebeziumMongoJsonParser { ) }) .context("Debezium Mongo needs a `_id` column with supported types (Varchar Jsonb int32 int64) in table")?.clone(); - let payload_column = rw_columns + let _payload_column = rw_columns .iter() .find(|desc| desc.name == "payload" && matches!(desc.data_type, DataType::Jsonb)) .context("Debezium Mongo needs a `payload` column with supported types Jsonb in table")? .clone(); // _rw_{connector}_file/partition & _rw_{connector}_offset are created automatically. - if rw_columns.iter().filter(|desc| desc.is_visible()).count() != 2 { - bail!("Debezium Mongo needs no more columns except `_id` and `payload` in table"); + if rw_columns + .iter() + .filter(|desc| desc.is_visible() && desc.additional_column.column_type.is_none()) + .count() + != 2 + { + bail!("Debezium Mongo needs no more data columns except `_id` and `payload` in table"); } // encodings are fixed to MongoJson - let key_builder = - build_accessor_builder(EncodingProperties::MongoJson(JsonProperties::default()))?; - let payload_builder = - build_accessor_builder(EncodingProperties::MongoJson(JsonProperties::default()))?; + let key_builder = build_accessor_builder(EncodingProperties::MongoJson)?; + let payload_builder = build_accessor_builder(EncodingProperties::MongoJson)?; Ok(Self { rw_columns, - id_column, - payload_column, source_ctx, key_builder, payload_builder, diff --git a/src/connector/src/parser/debezium/simd_json_parser.rs b/src/connector/src/parser/debezium/simd_json_parser.rs index 63cb939d81238..f9738eb9e357e 100644 --- a/src/connector/src/parser/debezium/simd_json_parser.rs +++ b/src/connector/src/parser/debezium/simd_json_parser.rs @@ -41,7 +41,7 @@ impl DebeziumJsonAccessBuilder { impl AccessBuilder for DebeziumJsonAccessBuilder { #[allow(clippy::unused_async)] - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { self.value = Some(payload); let mut event: BorrowedValue<'_> = simd_json::to_borrowed_value(self.value.as_mut().unwrap()) @@ -79,7 +79,7 @@ impl DebeziumMongoJsonAccessBuilder { impl AccessBuilder for DebeziumMongoJsonAccessBuilder { #[allow(clippy::unused_async)] - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { self.value = Some(payload); let mut event: BorrowedValue<'_> = simd_json::to_borrowed_value(self.value.as_mut().unwrap()) @@ -99,8 +99,6 @@ impl AccessBuilder for DebeziumMongoJsonAccessBuilder { #[cfg(test)] mod tests { - use std::convert::TryInto; - use chrono::{NaiveDate, NaiveTime}; use risingwave_common::array::{Op, StructValue}; use risingwave_common::catalog::ColumnId; @@ -132,7 +130,6 @@ mod tests { async fn build_parser(rw_columns: Vec) -> DebeziumParser { let props = SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Json(JsonProperties { use_schema_registry: false, timestamptz_handling: None, @@ -532,7 +529,7 @@ mod tests { // For other overflow, the parsing succeeds but the type conversion fails // The errors are ignored and logged. res.unwrap(); - assert!(logs_contain("Expected type"), "{i}"); + assert!(logs_contain("expected type"), "{i}"); } else { // For f64 overflow, the parsing fails let e = res.unwrap_err(); diff --git a/src/connector/src/parser/json_parser.rs b/src/connector/src/parser/json_parser.rs index f010b8e6b7df6..4ed9af1d30dfc 100644 --- a/src/connector/src/parser/json_parser.rs +++ b/src/connector/src/parser/json_parser.rs @@ -12,29 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +// Note on this file: +// +// There's no struct named `JsonParser` anymore since #13707. `ENCODE JSON` will be +// dispatched to `PlainParser` or `UpsertParser` with `JsonAccessBuilder` instead. +// +// This file now only contains utilities and tests for JSON parsing. Also, to avoid +// rely on the internal implementation and allow that to be changed, the tests use +// `ByteStreamSourceParserImpl` to create a parser instance. + +use std::collections::BTreeMap; use anyhow::Context as _; -use apache_avro::Schema; -use itertools::{Either, Itertools}; -use jst::{convert_avro, Context}; -use risingwave_common::{bail, try_match_expand}; +use risingwave_connector_codec::JsonSchema; use risingwave_pb::plan_common::ColumnDesc; -use super::avro::schema_resolver::ConfluentSchemaResolver; use super::util::{bytes_from_url, get_kafka_topic}; -use super::{EncodingProperties, JsonProperties, SchemaRegistryAuth, SpecificParserConfig}; +use super::{JsonProperties, SchemaRegistryAuth}; use crate::error::ConnectorResult; -use crate::only_parse_payload; -use crate::parser::avro::util::avro_schema_to_column_descs; use crate::parser::unified::json::{JsonAccess, JsonParseOptions}; -use crate::parser::unified::util::apply_row_accessor_on_stream_chunk_writer; use crate::parser::unified::AccessImpl; -use crate::parser::{ - AccessBuilder, ByteStreamSourceParser, ParserFormat, SourceStreamChunkRowWriter, -}; +use crate::parser::AccessBuilder; use crate::schema::schema_registry::{handle_sr_list, Client}; -use crate::source::{SourceColumnDesc, SourceContext, SourceContextRef}; #[derive(Debug)] pub struct JsonAccessBuilder { @@ -45,7 +44,8 @@ pub struct JsonAccessBuilder { impl AccessBuilder for JsonAccessBuilder { #[allow(clippy::unused_async)] - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + // XXX: When will we enter this branch? if payload.is_empty() { self.value = Some("{}".into()); } else { @@ -78,126 +78,25 @@ impl JsonAccessBuilder { } } -/// Parser for JSON format -#[derive(Debug)] -pub struct JsonParser { - rw_columns: Vec, - source_ctx: SourceContextRef, - // If schema registry is used, the starting index of payload is 5. - payload_start_idx: usize, -} - -impl JsonParser { - pub fn new( - props: SpecificParserConfig, - rw_columns: Vec, - source_ctx: SourceContextRef, - ) -> ConnectorResult { - let json_config = try_match_expand!(props.encoding_config, EncodingProperties::Json)?; - let payload_start_idx = if json_config.use_schema_registry { - 5 - } else { - 0 - }; - Ok(Self { - rw_columns, - source_ctx, - payload_start_idx, - }) - } - - #[cfg(test)] - pub fn new_for_test(rw_columns: Vec) -> ConnectorResult { - Ok(Self { - rw_columns, - source_ctx: SourceContext::dummy().into(), - payload_start_idx: 0, - }) - } - - #[allow(clippy::unused_async)] - pub async fn parse_inner( - &self, - mut payload: Vec, - mut writer: SourceStreamChunkRowWriter<'_>, - ) -> ConnectorResult<()> { - let value = simd_json::to_borrowed_value(&mut payload[self.payload_start_idx..]) - .context("failed to parse json payload")?; - let values = if let simd_json::BorrowedValue::Array(arr) = value { - Either::Left(arr.into_iter()) - } else { - Either::Right(std::iter::once(value)) - }; - - let mut errors = Vec::new(); - for value in values { - let accessor = JsonAccess::new(value); - match apply_row_accessor_on_stream_chunk_writer(accessor, &mut writer) { - Ok(_) => {} - Err(err) => errors.push(err), - } - } - - if errors.is_empty() { - Ok(()) - } else { - // TODO(error-handling): multiple errors - bail!( - "failed to parse {} row(s) in a single json message: {}", - errors.len(), - errors.iter().format(", ") - ); - } - } -} - -pub async fn schema_to_columns( +pub async fn fetch_json_schema_and_map_to_columns( schema_location: &str, schema_registry_auth: Option, - props: &HashMap, + props: &BTreeMap, ) -> ConnectorResult> { let url = handle_sr_list(schema_location)?; let json_schema = if let Some(schema_registry_auth) = schema_registry_auth { let client = Client::new(url, &schema_registry_auth)?; let topic = get_kafka_topic(props)?; - let resolver = ConfluentSchemaResolver::new(client); - let content = resolver - .get_raw_schema_by_subject_name(&format!("{}-value", topic)) - .await? - .content; - serde_json::from_str(&content)? + let schema = client + .get_schema_by_subject(&format!("{}-value", topic)) + .await?; + JsonSchema::parse_str(&schema.content)? } else { let url = url.first().unwrap(); let bytes = bytes_from_url(url, None).await?; - serde_json::from_slice(&bytes)? + JsonSchema::parse_bytes(&bytes)? }; - let context = Context::default(); - let avro_schema = convert_avro(&json_schema, context).to_string(); - let schema = Schema::parse_str(&avro_schema).context("failed to parse avro schema")?; - avro_schema_to_column_descs(&schema) -} - -impl ByteStreamSourceParser for JsonParser { - fn columns(&self) -> &[SourceColumnDesc] { - &self.rw_columns - } - - fn source_ctx(&self) -> &SourceContext { - &self.source_ctx - } - - fn parser_format(&self) -> ParserFormat { - ParserFormat::Json - } - - async fn parse_one<'a>( - &'a mut self, - _key: Option>, - payload: Option>, - writer: SourceStreamChunkRowWriter<'a>, - ) -> ConnectorResult<()> { - only_parse_payload!(self, payload, writer) - } + json_schema.json_schema_to_columns().map_err(Into::into) } #[cfg(test)] @@ -213,13 +112,31 @@ mod tests { use risingwave_pb::plan_common::additional_column::ColumnType as AdditionalColumnType; use risingwave_pb::plan_common::{AdditionalColumn, AdditionalColumnKey}; - use super::JsonParser; - use crate::parser::upsert_parser::UpsertParser; + use crate::parser::test_utils::ByteStreamSourceParserImplTestExt as _; use crate::parser::{ - EncodingProperties, JsonProperties, ProtocolProperties, SourceColumnDesc, - SourceStreamChunkBuilder, SpecificParserConfig, + ByteStreamSourceParserImpl, CommonParserConfig, ParserConfig, ProtocolProperties, + SourceColumnDesc, SpecificParserConfig, }; - use crate::source::{SourceColumnType, SourceContext}; + use crate::source::SourceColumnType; + + fn make_parser(rw_columns: Vec) -> ByteStreamSourceParserImpl { + ByteStreamSourceParserImpl::create_for_test(ParserConfig { + common: CommonParserConfig { rw_columns }, + specific: SpecificParserConfig::DEFAULT_PLAIN_JSON, + }) + .unwrap() + } + + fn make_upsert_parser(rw_columns: Vec) -> ByteStreamSourceParserImpl { + ByteStreamSourceParserImpl::create_for_test(ParserConfig { + common: CommonParserConfig { rw_columns }, + specific: SpecificParserConfig { + protocol_config: ProtocolProperties::Upsert, + ..SpecificParserConfig::DEFAULT_PLAIN_JSON + }, + }) + .unwrap() + } fn get_payload() -> Vec> { vec![ @@ -249,21 +166,8 @@ mod tests { SourceColumnDesc::simple("interval", DataType::Interval, 11.into()), ]; - let parser = JsonParser::new( - SpecificParserConfig::DEFAULT_PLAIN_JSON, - descs.clone(), - SourceContext::dummy().into(), - ) - .unwrap(); - - let mut builder = SourceStreamChunkBuilder::with_capacity(descs, 2); - - for payload in get_payload() { - let writer = builder.row_writer(); - parser.parse_inner(payload, writer).await.unwrap(); - } - - let chunk = builder.finish(); + let parser = make_parser(descs); + let chunk = parser.parse(get_payload()).await; let mut rows = chunk.rows(); @@ -359,38 +263,20 @@ mod tests { SourceColumnDesc::simple("v2", DataType::Int16, 1.into()), SourceColumnDesc::simple("v3", DataType::Varchar, 2.into()), ]; - let parser = JsonParser::new( - SpecificParserConfig::DEFAULT_PLAIN_JSON, - descs.clone(), - SourceContext::dummy().into(), - ) - .unwrap(); - let mut builder = SourceStreamChunkBuilder::with_capacity(descs, 3); - - // Parse a correct record. - { - let writer = builder.row_writer(); - let payload = br#"{"v1": 1, "v2": 2, "v3": "3"}"#.to_vec(); - parser.parse_inner(payload, writer).await.unwrap(); - } - // Parse an incorrect record. - { - let writer = builder.row_writer(); + let parser = make_parser(descs); + let payloads = vec![ + // Parse a correct record. + br#"{"v1": 1, "v2": 2, "v3": "3"}"#.to_vec(), + // Parse an incorrect record. // `v2` overflowed. - let payload = br#"{"v1": 1, "v2": 65536, "v3": "3"}"#.to_vec(); // ignored the error, and fill None at v2. - parser.parse_inner(payload, writer).await.unwrap(); - } - - // Parse a correct record. - { - let writer = builder.row_writer(); - let payload = br#"{"v1": 1, "v2": 2, "v3": "3"}"#.to_vec(); - parser.parse_inner(payload, writer).await.unwrap(); - } + br#"{"v1": 1, "v2": 65536, "v3": "3"}"#.to_vec(), + // Parse a correct record. + br#"{"v1": 1, "v2": 2, "v3": "3"}"#.to_vec(), + ]; + let chunk = parser.parse(payloads).await; - let chunk = builder.finish(); assert!(chunk.valid()); assert_eq!(chunk.cardinality(), 3); @@ -430,12 +316,7 @@ mod tests { .map(SourceColumnDesc::from) .collect_vec(); - let parser = JsonParser::new( - SpecificParserConfig::DEFAULT_PLAIN_JSON, - descs.clone(), - SourceContext::dummy().into(), - ) - .unwrap(); + let parser = make_parser(descs); let payload = br#" { "data": { @@ -454,12 +335,8 @@ mod tests { "VarcharCastToI64": "1598197865760800768" } "#.to_vec(); - let mut builder = SourceStreamChunkBuilder::with_capacity(descs, 1); - { - let writer = builder.row_writer(); - parser.parse_inner(payload, writer).await.unwrap(); - } - let chunk = builder.finish(); + let chunk = parser.parse(vec![payload]).await; + let (op, row) = chunk.rows().next().unwrap(); assert_eq!(op, Op::Insert); let row = row.into_owned_row().into_inner(); @@ -502,24 +379,15 @@ mod tests { .map(SourceColumnDesc::from) .collect_vec(); - let parser = JsonParser::new( - SpecificParserConfig::DEFAULT_PLAIN_JSON, - descs.clone(), - SourceContext::dummy().into(), - ) - .unwrap(); + let parser = make_parser(descs); let payload = br#" { "struct": "{\"varchar\": \"varchar\", \"boolean\": true}" } "# .to_vec(); - let mut builder = SourceStreamChunkBuilder::with_capacity(descs, 1); - { - let writer = builder.row_writer(); - parser.parse_inner(payload, writer).await.unwrap(); - } - let chunk = builder.finish(); + let chunk = parser.parse(vec![payload]).await; + let (op, row) = chunk.rows().next().unwrap(); assert_eq!(op, Op::Insert); let row = row.into_owned_row().into_inner(); @@ -548,12 +416,7 @@ mod tests { .map(SourceColumnDesc::from) .collect_vec(); - let parser = JsonParser::new( - SpecificParserConfig::DEFAULT_PLAIN_JSON, - descs.clone(), - SourceContext::dummy().into(), - ) - .unwrap(); + let parser = make_parser(descs); let payload = br#" { "struct": { @@ -562,12 +425,8 @@ mod tests { } "# .to_vec(); - let mut builder = SourceStreamChunkBuilder::with_capacity(descs, 1); - { - let writer = builder.row_writer(); - parser.parse_inner(payload, writer).await.unwrap(); - } - let chunk = builder.finish(); + let chunk = parser.parse(vec![payload]).await; + let (op, row) = chunk.rows().next().unwrap(); assert_eq!(op, Op::Insert); let row = row.into_owned_row().into_inner(); @@ -589,7 +448,10 @@ mod tests { (r#"{"a":2}"#, r#"{"a":2,"b":2}"#), (r#"{"a":2}"#, r#""#), ] - .to_vec(); + .into_iter() + .map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec())) + .collect_vec(); + let key_column_desc = SourceColumnDesc { name: "rw_key".into(), data_type: DataType::Bytea, @@ -607,34 +469,9 @@ mod tests { SourceColumnDesc::simple("b", DataType::Int32, 1.into()), key_column_desc, ]; - let props = SpecificParserConfig { - key_encoding_config: None, - encoding_config: EncodingProperties::Json(JsonProperties { - use_schema_registry: false, - timestamptz_handling: None, - }), - protocol_config: ProtocolProperties::Upsert, - }; - let mut parser = UpsertParser::new(props, descs.clone(), SourceContext::dummy().into()) - .await - .unwrap(); - let mut builder = SourceStreamChunkBuilder::with_capacity(descs, 4); - for item in items { - parser - .parse_inner( - Some(item.0.as_bytes().to_vec()), - if !item.1.is_empty() { - Some(item.1.as_bytes().to_vec()) - } else { - None - }, - builder.row_writer(), - ) - .await - .unwrap(); - } - let chunk = builder.finish(); + let parser = make_upsert_parser(descs); + let chunk = parser.parse_upsert(items).await; // expected chunk // +---+---+---+------------------+ diff --git a/src/connector/src/parser/maxwell/mod.rs b/src/connector/src/parser/maxwell/mod.rs index bcbd0cc160e42..53b845260c421 100644 --- a/src/connector/src/parser/maxwell/mod.rs +++ b/src/connector/src/parser/maxwell/mod.rs @@ -13,6 +13,5 @@ // limitations under the License. mod maxwell_parser; -mod operators; pub use maxwell_parser::*; mod simd_json_parser; diff --git a/src/connector/src/parser/maxwell/simd_json_parser.rs b/src/connector/src/parser/maxwell/simd_json_parser.rs index 5db6cdd52e90c..e5bc7291ccf07 100644 --- a/src/connector/src/parser/maxwell/simd_json_parser.rs +++ b/src/connector/src/parser/maxwell/simd_json_parser.rs @@ -35,7 +35,6 @@ mod tests { ]; let props = SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Json(JsonProperties { use_schema_registry: false, timestamptz_handling: None, diff --git a/src/connector/src/parser/mod.rs b/src/connector/src/parser/mod.rs index f9c1081e61510..4e471f2a8833e 100644 --- a/src/connector/src/parser/mod.rs +++ b/src/connector/src/parser/mod.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use std::fmt::Debug; use std::sync::LazyLock; @@ -31,9 +31,10 @@ use risingwave_common::bail; use risingwave_common::catalog::{KAFKA_TIMESTAMP_COLUMN_NAME, TABLE_NAME_COLUMN_NAME}; use risingwave_common::log::LogSuppresser; use risingwave_common::metrics::GLOBAL_ERROR_METRICS; -use risingwave_common::types::{Datum, Scalar, ScalarImpl}; +use risingwave_common::types::{Datum, DatumCow, DatumRef, ScalarRefImpl}; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common::util::tracing::InstrumentStream; +use risingwave_connector_codec::decoder::avro::MapHandling; use risingwave_pb::catalog::{ SchemaRegistryNameStrategy as PbSchemaRegistryNameStrategy, StreamSourceInfo, }; @@ -46,7 +47,8 @@ pub use self::mysql::mysql_row_to_owned_row; use self::plain_parser::PlainParser; pub use self::postgres::postgres_row_to_owned_row; use self::simd_json_parser::DebeziumJsonAccessBuilder; -pub use self::unified::json::TimestamptzHandling; +pub use self::unified::json::{JsonAccess, TimestamptzHandling}; +pub use self::unified::Access; use self::unified::AccessImpl; use self::upsert_parser::UpsertParser; use self::util::get_kafka_topic; @@ -55,7 +57,8 @@ use crate::error::{ConnectorError, ConnectorResult}; use crate::parser::maxwell::MaxwellParser; use crate::parser::simd_json_parser::DebeziumMongoJsonAccessBuilder; use crate::parser::util::{ - extract_header_inner_from_meta, extract_headers_from_meta, extreact_timestamp_from_meta, + extract_cdc_meta_column, extract_header_inner_from_meta, extract_headers_from_meta, + extreact_timestamp_from_meta, }; use crate::schema::schema_registry::SchemaRegistryAuth; use crate::source::monitor::GLOBAL_SOURCE_METRICS; @@ -77,13 +80,15 @@ mod mysql; pub mod parquet_parser; pub mod plain_parser; mod postgres; + mod protobuf; +pub mod scalar_adapter; mod unified; mod upsert_parser; mod util; pub use debezium::DEBEZIUM_IGNORE_KEY; -use risingwave_common::buffer::BitmapBuilder; +use risingwave_common::bitmap::BitmapBuilder; pub use unified::{AccessError, AccessResult}; /// A builder for building a [`StreamChunk`] from [`SourceColumnDesc`]. @@ -91,6 +96,7 @@ pub struct SourceStreamChunkBuilder { descs: Vec, builders: Vec, op_builder: Vec, + vis_builder: BitmapBuilder, } impl SourceStreamChunkBuilder { @@ -104,6 +110,7 @@ impl SourceStreamChunkBuilder { descs, builders, op_builder: Vec::with_capacity(cap), + vis_builder: BitmapBuilder::with_capacity(cap), } } @@ -112,18 +119,21 @@ impl SourceStreamChunkBuilder { descs: &self.descs, builders: &mut self.builders, op_builder: &mut self.op_builder, + vis_builder: &mut self.vis_builder, + visible: true, // write visible rows by default row_meta: None, } } /// Consumes the builder and returns a [`StreamChunk`]. pub fn finish(self) -> StreamChunk { - StreamChunk::new( + StreamChunk::with_visibility( self.op_builder, self.builders .into_iter() .map(|builder| builder.finish().into()) .collect(), + self.vis_builder.finish(), ) } @@ -131,12 +141,12 @@ impl SourceStreamChunkBuilder { /// the builders of the next [`StreamChunk`]. #[must_use] pub fn take(&mut self, next_cap: usize) -> StreamChunk { - let descs = std::mem::take(&mut self.descs); + let descs = std::mem::take(&mut self.descs); // we don't use `descs` in `finish` let builder = std::mem::replace(self, Self::with_capacity(descs, next_cap)); builder.finish() } - pub fn op_num(&self) -> usize { + pub fn len(&self) -> usize { self.op_builder.len() } @@ -145,63 +155,6 @@ impl SourceStreamChunkBuilder { } } -/// A builder for building a [`StreamChunk`] that contains only heartbeat rows. -/// Some connectors may emit heartbeat messages to the downstream, and the cdc source -/// rely on the heartbeat messages to keep the source offset up-to-date with upstream. -pub struct HeartbeatChunkBuilder { - builder: SourceStreamChunkBuilder, -} - -impl HeartbeatChunkBuilder { - fn with_capacity(descs: Vec, cap: usize) -> Self { - let builders = descs - .iter() - .map(|desc| desc.data_type.create_array_builder(cap)) - .collect(); - - Self { - builder: SourceStreamChunkBuilder { - descs, - builders, - op_builder: Vec::with_capacity(cap), - }, - } - } - - fn row_writer(&mut self) -> SourceStreamChunkRowWriter<'_> { - self.builder.row_writer() - } - - /// Consumes the builder and returns a [`StreamChunk`] with all rows marked as invisible - fn finish(self) -> StreamChunk { - // heartbeat chunk should be invisible - let builder = self.builder; - let visibility = BitmapBuilder::zeroed(builder.op_builder.len()); - StreamChunk::with_visibility( - builder.op_builder, - builder - .builders - .into_iter() - .map(|builder| builder.finish().into()) - .collect(), - visibility.finish(), - ) - } - - /// Resets the builder and returns a [`StreamChunk`], while reserving `next_cap` capacity for - /// the builders of the next [`StreamChunk`]. - #[must_use] - fn take(&mut self, next_cap: usize) -> StreamChunk { - let descs = std::mem::take(&mut self.builder.descs); - let builder = std::mem::replace(self, Self::with_capacity(descs, next_cap)); - builder.finish() - } - - fn is_empty(&self) -> bool { - self.builder.is_empty() - } -} - /// `SourceStreamChunkRowWriter` is responsible to write one or more records to the [`StreamChunk`], /// where each contains either one row (Insert/Delete) or two rows (Update) that can be written atomically. /// @@ -215,6 +168,10 @@ pub struct SourceStreamChunkRowWriter<'a> { descs: &'a [SourceColumnDesc], builders: &'a mut [ArrayBuilderImpl], op_builder: &'a mut Vec, + vis_builder: &'a mut BitmapBuilder, + + /// Whether the rows written by this writer should be visible in output `StreamChunk`. + visible: bool, /// An optional meta data of the original message. /// @@ -222,6 +179,22 @@ pub struct SourceStreamChunkRowWriter<'a> { row_meta: Option>, } +impl<'a> SourceStreamChunkRowWriter<'a> { + /// Set the meta data of the original message for this row writer. + /// + /// This should always be called except for tests. + fn with_meta(mut self, row_meta: MessageMeta<'a>) -> Self { + self.row_meta = Some(row_meta); + self + } + + /// Convert the row writer to invisible row writer. + fn invisible(mut self) -> Self { + self.visible = false; + self + } +} + /// The meta data of the original message for a row writer. /// /// Extracted from the `SourceMessage`. @@ -232,17 +205,17 @@ pub struct MessageMeta<'a> { offset: &'a str, } -impl MessageMeta<'_> { +impl<'a> MessageMeta<'a> { /// Extract the value for the given column. /// /// Returns `None` if the column is not a meta column. - fn value_for_column(self, desc: &SourceColumnDesc) -> Option { - match desc.column_type { + fn value_for_column(self, desc: &SourceColumnDesc) -> Option> { + let datum: DatumRef<'_> = match desc.column_type { // Row id columns are filled with `NULL` here and will be filled with the real // row id generated by `RowIdGenExecutor` later. - SourceColumnType::RowId => Datum::None.into(), + SourceColumnType::RowId => None, // Extract the offset from the meta data. - SourceColumnType::Offset => Datum::Some(self.offset.into()).into(), + SourceColumnType::Offset => Some(self.offset.into()), // Extract custom meta data per connector. SourceColumnType::Meta if let SourceMeta::Kafka(kafka_meta) = self.meta => { assert_eq!( @@ -250,14 +223,11 @@ impl MessageMeta<'_> { KAFKA_TIMESTAMP_COLUMN_NAME, "unexpected kafka meta column name" ); - kafka_meta - .timestamp - .map(|ts| { - risingwave_common::cast::i64_to_timestamptz(ts) - .unwrap() - .to_scalar_value() - }) - .into() + kafka_meta.timestamp.map(|ts| { + risingwave_common::cast::i64_to_timestamptz(ts) + .unwrap() + .into() + }) } SourceColumnType::Meta if let SourceMeta::DebeziumCdc(cdc_meta) = self.meta => { assert_eq!( @@ -265,21 +235,23 @@ impl MessageMeta<'_> { TABLE_NAME_COLUMN_NAME, "unexpected cdc meta column name" ); - Datum::Some(cdc_meta.full_table_name.as_str().into()).into() + Some(cdc_meta.full_table_name.as_str().into()) } // For other cases, return `None`. - SourceColumnType::Meta | SourceColumnType::Normal => None, - } + SourceColumnType::Meta | SourceColumnType::Normal => return None, + }; + + Some(datum) } } trait OpAction { - type Output; + type Output<'a>; - fn output_for(datum: Datum) -> Self::Output; + fn output_for<'a>(datum: impl Into>) -> Self::Output<'a>; - fn apply(builder: &mut ArrayBuilderImpl, output: Self::Output); + fn apply(builder: &mut ArrayBuilderImpl, output: Self::Output<'_>); fn rollback(builder: &mut ArrayBuilderImpl); @@ -289,16 +261,16 @@ trait OpAction { struct OpActionInsert; impl OpAction for OpActionInsert { - type Output = Datum; + type Output<'a> = DatumCow<'a>; #[inline(always)] - fn output_for(datum: Datum) -> Self::Output { - datum + fn output_for<'a>(datum: impl Into>) -> Self::Output<'a> { + datum.into() } #[inline(always)] - fn apply(builder: &mut ArrayBuilderImpl, output: Datum) { - builder.append(&output) + fn apply(builder: &mut ArrayBuilderImpl, output: DatumCow<'_>) { + builder.append(output) } #[inline(always)] @@ -308,23 +280,23 @@ impl OpAction for OpActionInsert { #[inline(always)] fn finish(writer: &mut SourceStreamChunkRowWriter<'_>) { - writer.op_builder.push(Op::Insert); + writer.append_op(Op::Insert); } } struct OpActionDelete; impl OpAction for OpActionDelete { - type Output = Datum; + type Output<'a> = DatumCow<'a>; #[inline(always)] - fn output_for(datum: Datum) -> Self::Output { - datum + fn output_for<'a>(datum: impl Into>) -> Self::Output<'a> { + datum.into() } #[inline(always)] - fn apply(builder: &mut ArrayBuilderImpl, output: Datum) { - builder.append(&output) + fn apply(builder: &mut ArrayBuilderImpl, output: DatumCow<'_>) { + builder.append(output) } #[inline(always)] @@ -334,24 +306,25 @@ impl OpAction for OpActionDelete { #[inline(always)] fn finish(writer: &mut SourceStreamChunkRowWriter<'_>) { - writer.op_builder.push(Op::Delete); + writer.append_op(Op::Delete); } } struct OpActionUpdate; impl OpAction for OpActionUpdate { - type Output = (Datum, Datum); + type Output<'a> = (DatumCow<'a>, DatumCow<'a>); #[inline(always)] - fn output_for(datum: Datum) -> Self::Output { + fn output_for<'a>(datum: impl Into>) -> Self::Output<'a> { + let datum = datum.into(); (datum.clone(), datum) } #[inline(always)] - fn apply(builder: &mut ArrayBuilderImpl, output: (Datum, Datum)) { - builder.append(&output.0); - builder.append(&output.1); + fn apply(builder: &mut ArrayBuilderImpl, output: (DatumCow<'_>, DatumCow<'_>)) { + builder.append(output.0); + builder.append(output.1); } #[inline(always)] @@ -362,28 +335,50 @@ impl OpAction for OpActionUpdate { #[inline(always)] fn finish(writer: &mut SourceStreamChunkRowWriter<'_>) { - writer.op_builder.push(Op::UpdateDelete); - writer.op_builder.push(Op::UpdateInsert); + writer.append_op(Op::UpdateDelete); + writer.append_op(Op::UpdateInsert); } } -impl<'a> SourceStreamChunkRowWriter<'a> { - /// Set the meta data of the original message for this row writer. - /// - /// This should always be called except for tests. - fn with_meta(self, row_meta: MessageMeta<'a>) -> Self { - Self { - row_meta: Some(row_meta), - ..self - } +impl SourceStreamChunkRowWriter<'_> { + fn append_op(&mut self, op: Op) { + self.op_builder.push(op); + self.vis_builder.append(self.visible); } -} -impl SourceStreamChunkRowWriter<'_> { - fn do_action( - &mut self, - mut f: impl FnMut(&SourceColumnDesc) -> AccessResult, + fn do_action<'a, A: OpAction>( + &'a mut self, + mut f: impl FnMut(&SourceColumnDesc) -> AccessResult>, ) -> AccessResult<()> { + let mut parse_field = |desc: &SourceColumnDesc| { + match f(desc) { + Ok(output) => Ok(output), + + // Throw error for failed access to primary key columns. + Err(e) if desc.is_pk => Err(e), + // Ignore error for other columns and fill in `NULL` instead. + Err(error) => { + // TODO: figure out a way to fill in not-null default value if user specifies one + // TODO: decide whether the error should not be ignored (e.g., even not a valid Debezium message) + // TODO: not using tracing span to provide `split_id` and `offset` due to performance concern, + // see #13105 + static LOG_SUPPERSSER: LazyLock = + LazyLock::new(LogSuppresser::default); + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + error = %error.as_report(), + split_id = self.row_meta.as_ref().map(|m| m.split_id), + offset = self.row_meta.as_ref().map(|m| m.offset), + column = desc.name, + suppressed_count, + "failed to parse non-pk column, padding with `NULL`" + ); + } + Ok(A::output_for(Datum::None)) + } + } + }; + let mut wrapped_f = |desc: &SourceColumnDesc| { match (&desc.column_type, &desc.additional_column.column_type) { (&SourceColumnType::Offset | &SourceColumnType::RowId, _) => { @@ -409,20 +404,45 @@ impl SourceStreamChunkRowWriter<'_> { .unwrap(), // handled all match cases in internal match, unwrap is safe )); } - (_, &Some(AdditionalColumnType::Timestamp(_))) => { - return Ok(A::output_for( - self.row_meta - .as_ref() - .and_then(|ele| extreact_timestamp_from_meta(ele.meta)) - .unwrap_or(None), - )) + + ( + _, // for cdc tables + &Some(ref col @ AdditionalColumnType::DatabaseName(_)) + | &Some(ref col @ AdditionalColumnType::TableName(_)), + ) => { + match self.row_meta { + Some(row_meta) => { + if let SourceMeta::DebeziumCdc(cdc_meta) = row_meta.meta { + Ok(A::output_for(extract_cdc_meta_column( + cdc_meta, + col, + desc.name.as_str(), + )?)) + } else { + Err(AccessError::Uncategorized { + message: "CDC metadata not found in the message".to_string(), + }) + } + } + None => parse_field(desc), // parse from payload + } + } + (_, &Some(AdditionalColumnType::Timestamp(_))) => match self.row_meta { + Some(row_meta) => Ok(A::output_for( + extreact_timestamp_from_meta(row_meta.meta).unwrap_or(None), + )), + None => parse_field(desc), // parse from payload + }, + (_, &Some(AdditionalColumnType::CollectionName(_))) => { + // collection name for `mongodb-cdc` should be parsed from the message payload + parse_field(desc) } (_, &Some(AdditionalColumnType::Partition(_))) => { // the meta info does not involve spec connector return Ok(A::output_for( self.row_meta .as_ref() - .map(|ele| ScalarImpl::Utf8(ele.split_id.to_string().into())), + .map(|ele| ScalarRefImpl::Utf8(ele.split_id)), )); } (_, &Some(AdditionalColumnType::Offset(_))) => { @@ -430,7 +450,7 @@ impl SourceStreamChunkRowWriter<'_> { return Ok(A::output_for( self.row_meta .as_ref() - .map(|ele| ScalarImpl::Utf8(ele.offset.to_string().into())), + .map(|ele| ScalarRefImpl::Utf8(ele.offset)), )); } (_, &Some(AdditionalColumnType::HeaderInner(ref header_inner))) => { @@ -444,7 +464,7 @@ impl SourceStreamChunkRowWriter<'_> { header_inner.data_type.as_ref(), ) }) - .unwrap_or(None), + .unwrap_or(Datum::None.into()), )) } (_, &Some(AdditionalColumnType::Headers(_))) => { @@ -460,50 +480,25 @@ impl SourceStreamChunkRowWriter<'_> { return Ok(A::output_for( self.row_meta .as_ref() - .map(|ele| ScalarImpl::Utf8(ele.split_id.to_string().into())), + .map(|ele| ScalarRefImpl::Utf8(ele.split_id)), )); } (_, _) => { // For normal columns, call the user provided closure. - match f(desc) { - Ok(output) => Ok(output), - - // Throw error for failed access to primary key columns. - Err(e) if desc.is_pk => Err(e), - // Ignore error for other columns and fill in `NULL` instead. - Err(error) => { - // TODO: figure out a way to fill in not-null default value if user specifies one - // TODO: decide whether the error should not be ignored (e.g., even not a valid Debezium message) - // TODO: not using tracing span to provide `split_id` and `offset` due to performance concern, - // see #13105 - static LOG_SUPPERSSER: LazyLock = - LazyLock::new(LogSuppresser::default); - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::warn!( - error = %error.as_report(), - split_id = self.row_meta.as_ref().map(|m| m.split_id), - offset = self.row_meta.as_ref().map(|m| m.offset), - column = desc.name, - suppressed_count, - "failed to parse non-pk column, padding with `NULL`" - ); - } - Ok(A::output_for(Datum::None)) - } - } + parse_field(desc) } } }; // Columns that changes have been applied to. Used to rollback when an error occurs. - let mut applied_columns = Vec::with_capacity(self.descs.len()); + let mut applied_columns = 0; let result = (self.descs.iter()) .zip_eq_fast(self.builders.iter_mut()) .try_for_each(|(desc, builder)| { wrapped_f(desc).map(|output| { A::apply(builder, output); - applied_columns.push(builder); + applied_columns += 1; }) }); @@ -513,8 +508,8 @@ impl SourceStreamChunkRowWriter<'_> { Ok(()) } Err(e) => { - for builder in applied_columns { - A::rollback(builder); + for i in 0..applied_columns { + A::rollback(&mut self.builders[i]); } Err(e) } @@ -525,33 +520,46 @@ impl SourceStreamChunkRowWriter<'_> { /// produces one [`Datum`] by corresponding [`SourceColumnDesc`]. /// /// See the [struct-level documentation](SourceStreamChunkRowWriter) for more details. - pub fn insert( + #[inline(always)] + pub fn do_insert<'a, D>( &mut self, - f: impl FnMut(&SourceColumnDesc) -> AccessResult, - ) -> AccessResult<()> { - self.do_action::(f) + mut f: impl FnMut(&SourceColumnDesc) -> AccessResult, + ) -> AccessResult<()> + where + D: Into>, + { + self.do_action::(|desc| f(desc).map(Into::into)) } /// Write a `Delete` record to the [`StreamChunk`], with the given fallible closure that /// produces one [`Datum`] by corresponding [`SourceColumnDesc`]. /// /// See the [struct-level documentation](SourceStreamChunkRowWriter) for more details. - pub fn delete( + #[inline(always)] + pub fn do_delete<'a, D>( &mut self, - f: impl FnMut(&SourceColumnDesc) -> AccessResult, - ) -> AccessResult<()> { - self.do_action::(f) + mut f: impl FnMut(&SourceColumnDesc) -> AccessResult, + ) -> AccessResult<()> + where + D: Into>, + { + self.do_action::(|desc| f(desc).map(Into::into)) } /// Write a `Update` record to the [`StreamChunk`], with the given fallible closure that /// produces two [`Datum`]s as old and new value by corresponding [`SourceColumnDesc`]. /// /// See the [struct-level documentation](SourceStreamChunkRowWriter) for more details. - pub fn update( + #[inline(always)] + pub fn do_update<'a, D1, D2>( &mut self, - f: impl FnMut(&SourceColumnDesc) -> AccessResult<(Datum, Datum)>, - ) -> AccessResult<()> { - self.do_action::(f) + mut f: impl FnMut(&SourceColumnDesc) -> AccessResult<(D1, D2)>, + ) -> AccessResult<()> + where + D1: Into>, + D2: Into>, + { + self.do_action::(|desc| f(desc).map(|(old, new)| (old.into(), new.into()))) } } @@ -583,8 +591,10 @@ pub enum ParserFormat { Plain, } -/// `ByteStreamSourceParser` is a new message parser, the parser should consume -/// the input data stream and return a stream of parsed msgs. +/// `ByteStreamSourceParser` is the entrypoint abstraction for parsing messages. +/// It consumes bytes of one individual message and produces parsed records. +/// +/// It's used by [`ByteStreamSourceParserImpl::into_stream`]. `pub` is for benchmark only. pub trait ByteStreamSourceParser: Send + Debug + Sized + 'static { /// The column descriptors of the output chunk. fn columns(&self) -> &[SourceColumnDesc]; @@ -623,7 +633,7 @@ pub trait ByteStreamSourceParser: Send + Debug + Sized + 'static { } fn emit_empty_row<'a>(&'a mut self, mut writer: SourceStreamChunkRowWriter<'a>) { - _ = writer.insert(|_column| Ok(None)); + _ = writer.do_insert(|_column| Ok(Datum::None)); } } @@ -666,7 +676,7 @@ impl P { // The parser stream will be long-lived. We use `instrument_with` here to create // a new span for the polling of each chunk. - into_chunk_stream(self, data_stream) + into_chunk_stream_inner(self, data_stream) .instrument_with(move || tracing::info_span!("source_parse_chunk", actor_id, source_id)) } } @@ -678,10 +688,13 @@ const MAX_ROWS_FOR_TRANSACTION: usize = 4096; // TODO: when upsert is disabled, how to filter those empty payload // Currently, an err is returned for non upsert with empty payload #[try_stream(ok = StreamChunk, error = crate::error::ConnectorError)] -async fn into_chunk_stream(mut parser: P, data_stream: BoxSourceStream) { +async fn into_chunk_stream_inner( + mut parser: P, + data_stream: BoxSourceStream, +) { let columns = parser.columns().to_vec(); - let mut heartbeat_builder = HeartbeatChunkBuilder::with_capacity(columns.clone(), 0); + let mut heartbeat_builder = SourceStreamChunkBuilder::with_capacity(columns.clone(), 0); let mut builder = SourceStreamChunkBuilder::with_capacity(columns, 0); struct Transaction { @@ -689,13 +702,13 @@ async fn into_chunk_stream(mut parser: P, data_stream len: usize, } let mut current_transaction = None; - let mut yield_asap = false; // whether we should yield the chunk as soon as possible (txn commits) #[for_await] for batch in data_stream { let batch = batch?; let batch_len = batch.len(); + let mut last_batch_not_yielded = false; if let Some(Transaction { len, id }) = &mut current_transaction { // Dirty state. The last batch is not yielded due to uncommitted transaction. if *len > MAX_ROWS_FOR_TRANSACTION { @@ -706,17 +719,13 @@ async fn into_chunk_stream(mut parser: P, data_stream "transaction is larger than {MAX_ROWS_FOR_TRANSACTION} rows, force commit" ); *len = 0; // reset `len` while keeping `id` - yield_asap = false; yield builder.take(batch_len); } else { - // Normal transaction. After the transaction is committed, we should yield the last - // batch immediately, so set `yield_asap` to true. - yield_asap = true; + last_batch_not_yielded = true } } else { // Clean state. Reserve capacity for the builder. assert!(builder.is_empty()); - assert!(!yield_asap); let _ = builder.take(batch_len); } @@ -727,11 +736,14 @@ async fn into_chunk_stream(mut parser: P, data_stream offset = msg.offset, "got a empty message, could be a heartbeat" ); - parser.emit_empty_row(heartbeat_builder.row_writer().with_meta(MessageMeta { - meta: &msg.meta, - split_id: &msg.split_id, - offset: &msg.offset, - })); + // Emit an empty invisible row for the heartbeat message. + parser.emit_empty_row(heartbeat_builder.row_writer().invisible().with_meta( + MessageMeta { + meta: &msg.meta, + split_id: &msg.split_id, + offset: &msg.offset, + }, + )); continue; } @@ -745,7 +757,7 @@ async fn into_chunk_stream(mut parser: P, data_stream .observe(lag_ms as f64); } - let old_op_num = builder.op_num(); + let old_len = builder.len(); match parser .parse_one_with_txn( msg.key, @@ -761,12 +773,10 @@ async fn into_chunk_stream(mut parser: P, data_stream // It's possible that parsing multiple rows in a single message PARTIALLY failed. // We still have to maintain the row number in this case. res @ (Ok(ParseResult::Rows) | Err(_)) => { - // The number of rows added to the builder. - let num = builder.op_num() - old_op_num; - - // Aggregate the number of rows in the current transaction. + // Aggregate the number of new rows into the current transaction. if let Some(Transaction { len, .. }) = &mut current_transaction { - *len += num; + let n_new_rows = builder.len() - old_len; + *len += n_new_rows; } if let Err(error) = res { @@ -795,32 +805,28 @@ async fn into_chunk_stream(mut parser: P, data_stream } } - Ok(ParseResult::TransactionControl(txn_ctl)) => { - match txn_ctl { - TransactionControl::Begin { id } => { - if let Some(Transaction { id: current_id, .. }) = ¤t_transaction { - tracing::warn!(current_id, id, "already in transaction"); - } - tracing::debug!("begin upstream transaction: id={}", id); - current_transaction = Some(Transaction { id, len: 0 }); - } - TransactionControl::Commit { id } => { - let current_id = current_transaction.as_ref().map(|t| &t.id); - if current_id != Some(&id) { - tracing::warn!(?current_id, id, "transaction id mismatch"); - } - tracing::debug!("commit upstream transaction: id={}", id); - current_transaction = None; + Ok(ParseResult::TransactionControl(txn_ctl)) => match txn_ctl { + TransactionControl::Begin { id } => { + if let Some(Transaction { id: current_id, .. }) = ¤t_transaction { + tracing::warn!(current_id, id, "already in transaction"); } + tracing::debug!(id, "begin upstream transaction"); + current_transaction = Some(Transaction { id, len: 0 }); } + TransactionControl::Commit { id } => { + let current_id = current_transaction.as_ref().map(|t| &t.id); + if current_id != Some(&id) { + tracing::warn!(?current_id, id, "transaction id mismatch"); + } + tracing::debug!(id, "commit upstream transaction"); + current_transaction = None; - // Not in a transaction anymore and `yield_asap` is set, so we should yield the - // chunk now. - if current_transaction.is_none() && yield_asap { - yield_asap = false; - yield builder.take(batch_len - (i + 1)); + if last_batch_not_yielded { + yield builder.take(batch_len - (i + 1)); + last_batch_not_yielded = false; + } } - } + }, } } @@ -833,15 +839,14 @@ async fn into_chunk_stream(mut parser: P, data_stream // If we are not in a transaction, we should yield the chunk now. if current_transaction.is_none() { - yield_asap = false; - yield builder.take(0); } } } +/// Parses raw bytes into a specific format (avro, json, protobuf, ...), and then builds an [`Access`] from the parsed data. pub trait AccessBuilder { - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult>; + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult>; } #[derive(Debug)] @@ -886,10 +891,7 @@ impl AccessBuilderImpl { Ok(accessor) } - pub async fn generate_accessor( - &mut self, - payload: Vec, - ) -> ConnectorResult> { + pub async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { let accessor = match self { Self::Avro(builder) => builder.generate_accessor(payload).await?, Self::Protobuf(builder) => builder.generate_accessor(payload).await?, @@ -903,10 +905,11 @@ impl AccessBuilderImpl { } } +/// The entrypoint of parsing. It parses [`SourceMessage`] stream (byte stream) into [`StreamChunk`] stream. +/// Used by [`crate::source::into_chunk_stream`]. #[derive(Debug)] pub enum ByteStreamSourceParserImpl { Csv(CsvParser), - Json(JsonParser), Debezium(DebeziumParser), Plain(PlainParser), Upsert(UpsertParser), @@ -915,15 +918,12 @@ pub enum ByteStreamSourceParserImpl { CanalJson(CanalJsonParser), } -pub type ParsedStreamImpl = impl ChunkSourceStream + Unpin; - impl ByteStreamSourceParserImpl { - /// Converts this `SourceMessage` stream into a stream of [`StreamChunk`]. - pub fn into_stream(self, msg_stream: BoxSourceStream) -> ParsedStreamImpl { + /// Converts [`SourceMessage`] stream into [`StreamChunk`] stream. + pub fn into_stream(self, msg_stream: BoxSourceStream) -> impl ChunkSourceStream + Unpin { #[auto_enum(futures03::Stream)] let stream = match self { Self::Csv(parser) => parser.into_stream(msg_stream), - Self::Json(parser) => parser.into_stream(msg_stream), Self::Debezium(parser) => parser.into_stream(msg_stream), Self::DebeziumMongoJson(parser) => parser.into_stream(msg_stream), Self::Maxwell(parser) => parser.into_stream(msg_stream), @@ -977,8 +977,61 @@ impl ByteStreamSourceParserImpl { _ => unreachable!(), } } + + /// Create a parser for testing purposes. + pub fn create_for_test(parser_config: ParserConfig) -> ConnectorResult { + futures::executor::block_on(Self::create(parser_config, SourceContext::dummy().into())) + } +} + +/// Test utilities for [`ByteStreamSourceParserImpl`]. +#[cfg(test)] +pub mod test_utils { + use futures::StreamExt as _; + use itertools::Itertools as _; + + use super::*; + + #[easy_ext::ext(ByteStreamSourceParserImplTestExt)] + pub(crate) impl ByteStreamSourceParserImpl { + /// Parse the given payloads into a [`StreamChunk`]. + async fn parse(self, payloads: Vec>) -> StreamChunk { + let source_messages = payloads + .into_iter() + .map(|p| SourceMessage { + payload: (!p.is_empty()).then_some(p), + ..SourceMessage::dummy() + }) + .collect_vec(); + + self.into_stream(futures::stream::once(async { Ok(source_messages) }).boxed()) + .next() + .await + .unwrap() + .unwrap() + } + + /// Parse the given key-value pairs into a [`StreamChunk`]. + async fn parse_upsert(self, kvs: Vec<(Vec, Vec)>) -> StreamChunk { + let source_messages = kvs + .into_iter() + .map(|(k, v)| SourceMessage { + key: (!k.is_empty()).then_some(k), + payload: (!v.is_empty()).then_some(v), + ..SourceMessage::dummy() + }) + .collect_vec(); + + self.into_stream(futures::stream::once(async { Ok(source_messages) }).boxed()) + .next() + .await + .unwrap() + .unwrap() + } + } } +/// Note: this is created in `SourceReader::build_stream` #[derive(Debug, Clone, Default)] pub struct ParserConfig { pub common: CommonParserConfig, @@ -993,12 +1046,12 @@ impl ParserConfig { #[derive(Debug, Clone, Default)] pub struct CommonParserConfig { + /// Note: this is created by `SourceDescBuilder::builder` pub rw_columns: Vec, } #[derive(Debug, Clone, Default)] pub struct SpecificParserConfig { - pub key_encoding_config: Option, pub encoding_config: EncodingProperties, pub protocol_config: ProtocolProperties, } @@ -1006,7 +1059,6 @@ pub struct SpecificParserConfig { impl SpecificParserConfig { // for test only pub const DEFAULT_PLAIN_JSON: SpecificParserConfig = SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Json(JsonProperties { use_schema_registry: false, timestamptz_handling: None, @@ -1026,6 +1078,7 @@ pub struct AvroProperties { pub record_name: Option, pub key_record_name: Option, pub name_strategy: PbSchemaRegistryNameStrategy, + pub map_handling: Option, } #[derive(Debug, Default, Clone)] @@ -1064,7 +1117,7 @@ pub enum EncodingProperties { Protobuf(ProtobufProperties), Csv(CsvProperties), Json(JsonProperties), - MongoJson(JsonProperties), + MongoJson, Bytes(BytesProperties), Parquet, Native, @@ -1093,7 +1146,7 @@ impl SpecificParserConfig { // The validity of (format, encode) is ensured by `extract_format_encode` pub fn new( info: &StreamSourceInfo, - with_properties: &HashMap, + with_properties: &BTreeMap, ) -> ConnectorResult { let source_struct = extract_source_struct(info)?; let format = source_struct.format; @@ -1134,6 +1187,7 @@ impl SpecificParserConfig { .unwrap(), use_schema_registry: info.use_schema_registry, row_schema_location: info.row_schema_location.clone(), + map_handling: MapHandling::from_options(&info.format_encode_options)?, ..Default::default() }; if format == SourceFormat::Upsert { @@ -1227,7 +1281,6 @@ impl SpecificParserConfig { } }; Ok(Self { - key_encoding_config: None, encoding_config, protocol_config, }) diff --git a/src/connector/src/parser/mysql.rs b/src/connector/src/parser/mysql.rs index 5da838c8ff4fb..d1df27263e808 100644 --- a/src/connector/src/parser/mysql.rs +++ b/src/connector/src/parser/mysql.rs @@ -25,6 +25,8 @@ use risingwave_common::types::{ use rust_decimal::Decimal as RustDecimal; use thiserror_ext::AsReport; +use crate::parser::util::log_error; + static LOG_SUPPERSSER: LazyLock = LazyLock::new(LogSuppresser::default); macro_rules! handle_data_type { @@ -33,14 +35,7 @@ macro_rules! handle_data_type { match res { Ok(val) => val.map(|v| ScalarImpl::from(v)), Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = $name, - error = %err.as_report(), - suppressed_count, - "parse column failed", - ); - } + log_error!($name, err, "parse column failed"); None } } @@ -50,14 +45,7 @@ macro_rules! handle_data_type { match res { Ok(val) => val.map(|v| ScalarImpl::from(<$rw_type>::from(v))), Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = $name, - error = %err.as_report(), - suppressed_count, - "parse column failed", - ); - } + log_error!($name, err, "parse column failed"); None } } @@ -110,17 +98,12 @@ pub fn mysql_row_to_owned_row(mysql_row: &mut MysqlRow, schema: &Schema) -> Owne .unwrap_or(Ok(None)); match res { Ok(val) => val.map(|v| { - ScalarImpl::from(Timestamptz::from_micros(v.timestamp_micros())) + ScalarImpl::from(Timestamptz::from_micros( + v.and_utc().timestamp_micros(), + )) }), Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse column failed", - ); - } + log_error!(name, err, "parse column failed"); None } } @@ -132,14 +115,7 @@ pub fn mysql_row_to_owned_row(mysql_row: &mut MysqlRow, schema: &Schema) -> Owne match res { Ok(val) => val.map(|v| ScalarImpl::from(v.into_boxed_slice())), Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse column failed", - ); - } + log_error!(name, err, "parse column failed"); None } } diff --git a/src/connector/src/parser/parquet_parser.rs b/src/connector/src/parser/parquet_parser.rs index 43587769a04b8..d68162c9f1cd3 100644 --- a/src/connector/src/parser/parquet_parser.rs +++ b/src/connector/src/parser/parquet_parser.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use arrow_array::RecordBatch; use futures_async_stream::try_stream; -use opendal::Reader; +use opendal::{FuturesAsyncReader, Operator}; use risingwave_common::array::{ArrayBuilderImpl, DataChunk, StreamChunk}; use risingwave_common::types::{Datum, ScalarImpl}; @@ -44,7 +44,9 @@ impl ParquetParser { #[try_stream(boxed, ok = StreamChunk, error = crate::error::ConnectorError)] pub async fn into_stream( self, - record_batch_stream: parquet::arrow::async_reader::ParquetRecordBatchStream, + record_batch_stream: parquet::arrow::async_reader::ParquetRecordBatchStream< + FuturesAsyncReader, + >, file_name: String, ) { #[for_await] diff --git a/src/connector/src/parser/plain_parser.rs b/src/connector/src/parser/plain_parser.rs index c526366905938..f34fd29837174 100644 --- a/src/connector/src/parser/plain_parser.rs +++ b/src/connector/src/parser/plain_parser.rs @@ -15,6 +15,7 @@ use risingwave_common::bail; use super::unified::json::TimestamptzHandling; +use super::unified::kv_event::KvEvent; use super::{ AccessBuilderImpl, ByteStreamSourceParser, EncodingProperties, EncodingType, SourceStreamChunkRowWriter, SpecificParserConfig, @@ -23,13 +24,12 @@ use crate::error::ConnectorResult; use crate::parser::bytes_parser::BytesAccessBuilder; use crate::parser::simd_json_parser::DebeziumJsonAccessBuilder; use crate::parser::unified::debezium::parse_transaction_meta; -use crate::parser::unified::upsert::UpsertChangeEvent; -use crate::parser::unified::util::apply_row_operation_on_stream_chunk_writer_with_op; -use crate::parser::unified::{AccessImpl, ChangeEventOperation}; +use crate::parser::unified::AccessImpl; use crate::parser::upsert_parser::get_key_column_name; use crate::parser::{BytesProperties, ParseResult, ParserFormat}; use crate::source::{SourceColumnDesc, SourceContext, SourceContextRef, SourceMeta}; +/// Parser for `FORMAT PLAIN`, i.e., append-only source. #[derive(Debug)] pub struct PlainParser { pub key_builder: Option, @@ -102,30 +102,22 @@ impl PlainParser { }; } - // reuse upsert component but always insert - let mut row_op: UpsertChangeEvent, AccessImpl<'_, '_>> = - UpsertChangeEvent::default(); - let change_event_op = ChangeEventOperation::Upsert; + let mut row_op: KvEvent, AccessImpl<'_>> = KvEvent::default(); if let Some(data) = key && let Some(key_builder) = self.key_builder.as_mut() { // key is optional in format plain - row_op = row_op.with_key(key_builder.generate_accessor(data).await?); + row_op.with_key(key_builder.generate_accessor(data).await?); } if let Some(data) = payload { // the data part also can be an empty vec - row_op = row_op.with_value(self.payload_builder.generate_accessor(data).await?); + row_op.with_value(self.payload_builder.generate_accessor(data).await?); } - Ok( - apply_row_operation_on_stream_chunk_writer_with_op( - row_op, - &mut writer, - change_event_op, - ) - .map(|_| ParseResult::Rows)?, - ) + writer.do_insert(|column: &SourceColumnDesc| row_op.access_field(column))?; + + Ok(ParseResult::Rows) } } @@ -210,7 +202,7 @@ mod tests { let mut transactional = false; // for untransactional source, we expect emit a chunk for each message batch let message_stream = source_message_stream(transactional); - let chunk_stream = crate::parser::into_chunk_stream(parser, message_stream.boxed()); + let chunk_stream = crate::parser::into_chunk_stream_inner(parser, message_stream.boxed()); let output: std::result::Result, _> = block_on(chunk_stream.collect::>()) .into_iter() .collect(); @@ -247,7 +239,7 @@ mod tests { // for transactional source, we expect emit a single chunk for the transaction transactional = true; let message_stream = source_message_stream(transactional); - let chunk_stream = crate::parser::into_chunk_stream(parser, message_stream.boxed()); + let chunk_stream = crate::parser::into_chunk_stream_inner(parser, message_stream.boxed()); let output: std::result::Result, _> = block_on(chunk_stream.collect::>()) .into_iter() .collect(); @@ -286,11 +278,11 @@ mod tests { if i == 0 { // put begin message at first source_msg_batch.push(SourceMessage { - meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta { - full_table_name: "orders".to_string(), - source_ts_ms: 0, - is_transaction_meta: transactional, - }), + meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta::new( + "orders".to_string(), + 0, + transactional, + )), split_id: SplitId::from("1001"), offset: "0".into(), key: None, @@ -300,11 +292,11 @@ mod tests { // put data messages for data_msg in batch { source_msg_batch.push(SourceMessage { - meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta { - full_table_name: "orders".to_string(), - source_ts_ms: 0, - is_transaction_meta: false, - }), + meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta::new( + "orders".to_string(), + 0, + false, + )), split_id: SplitId::from("1001"), offset: "0".into(), key: None, @@ -314,11 +306,11 @@ mod tests { if i == data_batches.len() - 1 { // put commit message at last source_msg_batch.push(SourceMessage { - meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta { - full_table_name: "orders".to_string(), - source_ts_ms: 0, - is_transaction_meta: transactional, - }), + meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta::new( + "orders".to_string(), + 0, + transactional, + )), split_id: SplitId::from("1001"), offset: "0".into(), key: None, @@ -363,11 +355,7 @@ mod tests { let begin_msg = r#"{"schema":null,"payload":{"status":"BEGIN","id":"3E11FA47-71CA-11E1-9E33-C80AA9429562:23","event_count":null,"data_collections":null,"ts_ms":1704269323180}}"#; let commit_msg = r#"{"schema":null,"payload":{"status":"END","id":"3E11FA47-71CA-11E1-9E33-C80AA9429562:23","event_count":11,"data_collections":[{"data_collection":"public.orders_tx","event_count":5},{"data_collection":"public.person","event_count":6}],"ts_ms":1704269323180}}"#; - let cdc_meta = SourceMeta::DebeziumCdc(DebeziumCdcMeta { - full_table_name: "orders".to_string(), - source_ts_ms: 0, - is_transaction_meta: true, - }); + let cdc_meta = SourceMeta::DebeziumCdc(DebeziumCdcMeta::new("orders".to_string(), 0, true)); let msg_meta = MessageMeta { meta: &cdc_meta, split_id: "1001", diff --git a/src/connector/src/parser/postgres.rs b/src/connector/src/parser/postgres.rs index fa6c9a80952fb..da17ea256ba3c 100644 --- a/src/connector/src/parser/postgres.rs +++ b/src/connector/src/parser/postgres.rs @@ -12,69 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::str::FromStr; use std::sync::LazyLock; -use chrono::{NaiveDate, Utc}; -use pg_bigdecimal::PgNumeric; use risingwave_common::catalog::Schema; use risingwave_common::log::LogSuppresser; use risingwave_common::row::OwnedRow; -use risingwave_common::types::{ - DataType, Date, Decimal, Int256, Interval, JsonbVal, ListValue, ScalarImpl, Time, Timestamp, - Timestamptz, -}; -use rust_decimal::Decimal as RustDecimal; +use risingwave_common::types::{DataType, Decimal, ScalarImpl}; use thiserror_ext::AsReport; -use tokio_postgres::types::Type; -static LOG_SUPPERSSER: LazyLock = LazyLock::new(LogSuppresser::default); +use crate::parser::scalar_adapter::ScalarAdapter; +use crate::parser::util::log_error; -macro_rules! handle_list_data_type { - ($row:expr, $i:expr, $name:expr, $type:ty, $builder:expr) => { - let res = $row.try_get::<_, Option>>($i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter() - .for_each(|val| $builder.append(Some(ScalarImpl::from(val)))) - } - } - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = $name, - error = %err.as_report(), - suppressed_count, - "parse column failed", - ); - } - } - } - }; - ($row:expr, $i:expr, $name:expr, $type:ty, $builder:expr, $rw_type:ty) => { - let res = $row.try_get::<_, Option>>($i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - $builder.append(Some(ScalarImpl::from(<$rw_type>::from(val)))) - }) - } - } - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = $name, - error = %err.as_report(), - suppressed_count, - "parse column failed", - ); - } - } - } - }; -} +static LOG_SUPPERSSER: LazyLock = LazyLock::new(LogSuppresser::default); macro_rules! handle_data_type { ($row:expr, $i:expr, $name:expr, $type:ty) => {{ @@ -82,31 +31,7 @@ macro_rules! handle_data_type { match res { Ok(val) => val.map(|v| ScalarImpl::from(v)), Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = $name, - error = %err.as_report(), - suppressed_count, - "parse column failed", - ); - } - None - } - } - }}; - ($row:expr, $i:expr, $name:expr, $type:ty, $rw_type:ty) => {{ - let res = $row.try_get::<_, Option<$type>>($i); - match res { - Ok(val) => val.map(|v| ScalarImpl::from(<$rw_type>::from(v))), - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = $name, - error = %err.as_report(), - suppressed_count, - "parse column failed", - ); - } + log_error!($name, err, "parse column failed"); None } } @@ -118,349 +43,156 @@ pub fn postgres_row_to_owned_row(row: tokio_postgres::Row, schema: &Schema) -> O for i in 0..schema.fields.len() { let rw_field = &schema.fields[i]; let name = rw_field.name.as_str(); - let datum = { - match &rw_field.data_type { - DataType::Boolean => { - handle_data_type!(row, i, name, bool) - } - DataType::Int16 => { - handle_data_type!(row, i, name, i16) - } - DataType::Int32 => { - handle_data_type!(row, i, name, i32) - } - DataType::Int64 => { - handle_data_type!(row, i, name, i64) - } - DataType::Float32 => { - handle_data_type!(row, i, name, f32) - } - DataType::Float64 => { - handle_data_type!(row, i, name, f64) - } - DataType::Decimal => { - handle_data_type!(row, i, name, RustDecimal, Decimal) - } - DataType::Int256 => { - // Currently in order to handle the decimal beyond RustDecimal, - // we use the PgNumeric type to convert the decimal to a string. - // Then we convert the string to Int256. - // Note: It's only used to map the numeric type in upstream Postgres to RisingWave's rw_int256. - let res = row.try_get::<_, Option>(i); - match res { - Ok(val) => pg_numeric_to_rw_int256(val), - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = name, - error = %err.as_report(), - suppressed_count, - "parse numeric column as pg_numeric failed", - ); - } - None - } - } - } - DataType::Varchar => { - match *row.columns()[i].type_() { - // Since we don't support UUID natively, adapt it to a VARCHAR column - Type::UUID => { - let res = row.try_get::<_, Option>(i); - match res { - Ok(val) => val.map(|v| ScalarImpl::from(v.to_string())), - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse uuid column failed", - ); - } - None - } - } - } - // we support converting NUMERIC to VARCHAR implicitly - Type::NUMERIC => { - // Currently in order to handle the decimal beyond RustDecimal, - // we use the PgNumeric type to convert the decimal to a string. - // Note: It's only used to map the numeric type in upstream Postgres to RisingWave's varchar. - let res = row.try_get::<_, Option>(i); - match res { - Ok(val) => pg_numeric_to_varchar(val), - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - column = name, - error = %err.as_report(), - suppressed_count, - "parse numeric column as pg_numeric failed", - ); - } - None - } - } - } - _ => { - handle_data_type!(row, i, name, String) - } - } - } - DataType::Date => { - handle_data_type!(row, i, name, NaiveDate, Date) - } - DataType::Time => { - handle_data_type!(row, i, name, chrono::NaiveTime, Time) - } - DataType::Timestamp => { - handle_data_type!(row, i, name, chrono::NaiveDateTime, Timestamp) - } - DataType::Timestamptz => { - handle_data_type!(row, i, name, chrono::DateTime, Timestamptz) - } - DataType::Bytea => { - let res = row.try_get::<_, Option>>(i); - match res { - Ok(val) => val.map(|v| ScalarImpl::from(v.into_boxed_slice())), - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse column failed", - ); - } - None - } - } - } - DataType::Jsonb => { - handle_data_type!(row, i, name, serde_json::Value, JsonbVal) - } - DataType::Interval => { - handle_data_type!(row, i, name, Interval) - } - DataType::List(dtype) => { - let mut builder = dtype.create_array_builder(0); - match **dtype { - DataType::Boolean => { - handle_list_data_type!(row, i, name, bool, builder); - } - DataType::Int16 => { - handle_list_data_type!(row, i, name, i16, builder); - } - DataType::Int32 => { - handle_list_data_type!(row, i, name, i32, builder); - } - DataType::Int64 => { - handle_list_data_type!(row, i, name, i64, builder); - } - DataType::Float32 => { - handle_list_data_type!(row, i, name, f32, builder); - } - DataType::Float64 => { - handle_list_data_type!(row, i, name, f64, builder); - } - DataType::Decimal => { - handle_list_data_type!(row, i, name, RustDecimal, builder, Decimal); - } - DataType::Date => { - handle_list_data_type!(row, i, name, NaiveDate, builder, Date); - } - DataType::Varchar => { - match *row.columns()[i].type_() { - // Since we don't support UUID natively, adapt it to a VARCHAR column - Type::UUID_ARRAY => { - let res = row.try_get::<_, Option>>(i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(Some(ScalarImpl::from( - val.to_string(), - ))) - }); - } - } - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse uuid column failed", - ); - } - } - }; - } - Type::NUMERIC_ARRAY => { - let res = row.try_get::<_, Option>>(i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(pg_numeric_to_varchar(Some(val))) - }); - } - } - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse numeric list column as pg_numeric list failed", - ); - } - } - }; - } - _ => { - handle_list_data_type!(row, i, name, String, builder); - } - } - } - DataType::Time => { - handle_list_data_type!(row, i, name, chrono::NaiveTime, builder, Time); - } - DataType::Timestamp => { - handle_list_data_type!( - row, - i, - name, - chrono::NaiveDateTime, - builder, - Timestamp - ); - } - DataType::Timestamptz => { - handle_list_data_type!( - row, - i, - name, - chrono::DateTime, - builder, - Timestamptz - ); - } - DataType::Interval => { - handle_list_data_type!(row, i, name, Interval, builder); - } - DataType::Jsonb => { - handle_list_data_type!( - row, - i, - name, - serde_json::Value, - builder, - JsonbVal - ); - } - DataType::Bytea => { - let res = row.try_get::<_, Option>>>(i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(Some(ScalarImpl::from( - val.into_boxed_slice(), - ))) - }) - } - } - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse column failed", - ); - } - } - } - } - DataType::Int256 => { - let res = row.try_get::<_, Option>>(i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(pg_numeric_to_rw_int256(Some(val))) - }); - } - } - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - suppressed_count, - column = name, - error = %err.as_report(), - "parse numeric list column as pg_numeric list failed", - ); - } - } - }; - } - DataType::Struct(_) | DataType::List(_) | DataType::Serial => { - tracing::warn!( - "unsupported List data type {:?}, set the List to empty", - **dtype - ); - } - }; - - Some(ScalarImpl::from(ListValue::new(builder.finish()))) - } - DataType::Struct(_) | DataType::Serial => { - // Interval, Struct, List are not supported - tracing::warn!(rw_field.name, ?rw_field.data_type, "unsupported data type, set to null"); - None - } - } - }; + let datum = postgres_cell_to_scalar_impl(&row, &rw_field.data_type, i, name); datums.push(datum); } OwnedRow::new(datums) } -fn pg_numeric_to_rw_int256(val: Option) -> Option { - let string = pg_numeric_to_string(val)?; - match Int256::from_str(string.as_str()) { - Ok(num) => Some(ScalarImpl::from(num)), - Err(err) => { - if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { - tracing::error!( - error = %err.as_report(), - suppressed_count, - "parse numeric string as rw_int256 failed", +fn postgres_cell_to_scalar_impl( + row: &tokio_postgres::Row, + data_type: &DataType, + i: usize, + name: &str, +) -> Option { + // We observe several incompatibility issue in Debezium's Postgres connector. We summarize them here: + // Issue #1. The null of enum list is not supported in Debezium. An enum list contains `NULL` will fallback to `NULL`. + // Issue #2. In our parser, when there's inf, -inf, nan or invalid item in a list, the whole list will fallback null. + match data_type { + DataType::Boolean + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Float32 + | DataType::Float64 + | DataType::Date + | DataType::Time + | DataType::Timestamp + | DataType::Timestamptz + | DataType::Jsonb + | DataType::Interval + | DataType::Bytea => { + // ScalarAdapter is also fine. But ScalarImpl is more efficient + let res = row.try_get::<_, Option>(i); + match res { + Ok(val) => val, + Err(err) => { + log_error!(name, err, "parse column failed"); + None + } + } + } + DataType::Decimal => { + // Decimal is more efficient than PgNumeric in ScalarAdapter + handle_data_type!(row, i, name, Decimal) + } + DataType::Varchar | DataType::Int256 => { + let res = row.try_get::<_, Option>(i); + match res { + Ok(val) => val.and_then(|v| v.into_scalar(data_type)), + Err(err) => { + log_error!(name, err, "parse column failed"); + None + } + } + } + DataType::List(dtype) => match **dtype { + // TODO(Kexiang): allow DataType::List(_) + DataType::Struct(_) | DataType::List(_) | DataType::Serial => { + tracing::warn!( + "unsupported List data type {:?}, set the List to empty", + **dtype ); + None } + _ => { + let res = row.try_get::<_, Option>(i); + match res { + Ok(val) => val.and_then(|v| v.into_scalar(data_type)), + Err(err) => { + log_error!(name, err, "parse list column failed"); + None + } + } + } + }, + DataType::Struct(_) | DataType::Serial => { + // Struct and Serial are not supported + tracing::warn!(name, ?data_type, "unsupported data type, set to null"); None } } } -fn pg_numeric_to_varchar(val: Option) -> Option { - pg_numeric_to_string(val).map(ScalarImpl::from) -} +#[cfg(test)] +mod tests { + use tokio_postgres::NoTls; -fn pg_numeric_to_string(val: Option) -> Option { - if let Some(pg_numeric) = val { - // TODO(kexiang): NEGATIVE_INFINITY -> -Infinity, POSITIVE_INFINITY -> Infinity, NAN -> NaN - // The current implementation is to ensure consistency with the behavior of cdc event parsor. - match pg_numeric { - PgNumeric::NegativeInf => Some(String::from("NEGATIVE_INFINITY")), - PgNumeric::Normalized(big_decimal) => Some(big_decimal.to_string()), - PgNumeric::PositiveInf => Some(String::from("POSITIVE_INFINITY")), - PgNumeric::NaN => Some(String::from("NAN")), - } - } else { - // NULL - None + use crate::parser::scalar_adapter::EnumString; + const DB: &str = "postgres"; + const USER: &str = "kexiang"; + + #[ignore] + #[tokio::test] + async fn enum_string_integration_test() { + let connect = format!( + "host=localhost port=5432 user={} password={} dbname={}", + USER, DB, DB + ); + let (client, connection) = tokio_postgres::connect(connect.as_str(), NoTls) + .await + .unwrap(); + + // The connection object performs the actual communication with the database, + // so spawn it off to run on its own. + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + + // allow type existed + let _ = client + .execute("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy')", &[]) + .await; + client + .execute( + "CREATE TABLE IF NOT EXISTS person(id int PRIMARY KEY, current_mood mood)", + &[], + ) + .await + .unwrap(); + client.execute("DELETE FROM person;", &[]).await.unwrap(); + client + .execute("INSERT INTO person VALUES (1, 'happy')", &[]) + .await + .unwrap(); + + // test from_sql + let got: EnumString = client + .query_one("SELECT * FROM person", &[]) + .await + .unwrap() + .get::>(1) + .unwrap(); + assert_eq!("happy", got.0.as_str()); + + client.execute("DELETE FROM person", &[]).await.unwrap(); + + // test to_sql + client + .execute("INSERT INTO person VALUES (2, $1)", &[&got]) + .await + .unwrap(); + + let got_new: EnumString = client + .query_one("SELECT * FROM person", &[]) + .await + .unwrap() + .get::>(1) + .unwrap(); + assert_eq!("happy", got_new.0.as_str()); + client.execute("DROP TABLE person", &[]).await.unwrap(); + client.execute("DROP TYPE mood", &[]).await.unwrap(); } } diff --git a/src/connector/src/parser/protobuf/parser.rs b/src/connector/src/parser/protobuf/parser.rs index e9ae317ca5ebc..8be25074f6295 100644 --- a/src/connector/src/parser/protobuf/parser.rs +++ b/src/connector/src/parser/protobuf/parser.rs @@ -21,7 +21,10 @@ use prost_reflect::{ MessageDescriptor, ReflectMessage, Value, }; use risingwave_common::array::{ListValue, StructValue}; -use risingwave_common::types::{DataType, Datum, Decimal, JsonbVal, ScalarImpl, F32, F64}; +use risingwave_common::types::{ + DataType, Datum, DatumCow, Decimal, JsonbRef, JsonbVal, ScalarImpl, ScalarRefImpl, ToDatumRef, + ToOwnedDatum, F32, F64, +}; use risingwave_common::{bail, try_match_expand}; use risingwave_pb::plan_common::{AdditionalColumn, ColumnDesc, ColumnDescVersion}; use thiserror::Error; @@ -46,7 +49,7 @@ pub struct ProtobufAccessBuilder { impl AccessBuilder for ProtobufAccessBuilder { #[allow(clippy::unused_async)] - async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { + async fn generate_accessor(&mut self, payload: Vec) -> ConnectorResult> { let payload = if self.confluent_wire_type { resolve_pb_header(&payload)? } else { @@ -344,14 +347,20 @@ fn recursive_parse_json( serde_json::Value::Object(ret) } -pub fn from_protobuf_value( +pub fn from_protobuf_value<'a>( field_desc: &FieldDescriptor, - value: &Value, + value: &'a Value, descriptor_pool: &Arc, -) -> AccessResult { +) -> AccessResult> { let kind = field_desc.kind(); - let v = match value { + macro_rules! borrowed { + ($v:expr) => { + return Ok(DatumCow::Borrowed(Some($v.into()))) + }; + } + + let v: ScalarImpl = match value { Value::Bool(v) => ScalarImpl::Bool(*v), Value::I32(i) => ScalarImpl::Int32(*i), Value::U32(i) => ScalarImpl::Int64(*i as i64), @@ -359,7 +368,7 @@ pub fn from_protobuf_value( Value::U64(i) => ScalarImpl::Decimal(Decimal::from(*i)), Value::F32(f) => ScalarImpl::Float32(F32::from(*f)), Value::F64(f) => ScalarImpl::Float64(F64::from(*f)), - Value::String(s) => ScalarImpl::Utf8(s.as_str().into()), + Value::String(s) => borrowed!(s.as_str()), Value::EnumNumber(idx) => { let enum_desc = kind.as_enum().ok_or_else(|| AccessError::TypeError { expected: "enum".to_owned(), @@ -375,9 +384,7 @@ pub fn from_protobuf_value( if dyn_msg.descriptor().full_name() == "google.protobuf.Any" { // If the fields are not presented, default value is an empty string if !dyn_msg.has_field_by_name("type_url") || !dyn_msg.has_field_by_name("value") { - return Ok(Some(ScalarImpl::Jsonb(JsonbVal::from( - serde_json::json! {""}, - )))); + borrowed!(JsonbRef::empty_string()); } // Sanity check @@ -391,9 +398,8 @@ pub fn from_protobuf_value( let payload_field_desc = dyn_msg.descriptor().get_field_by_name("value").unwrap(); - let Some(ScalarImpl::Bytea(payload)) = - from_protobuf_value(&payload_field_desc, &payload, descriptor_pool)? - else { + let payload = from_protobuf_value(&payload_field_desc, &payload, descriptor_pool)?; + let Some(ScalarRefImpl::Bytea(payload)) = payload.to_datum_ref() else { bail_uncategorized!("expected bytes for dynamic message payload"); }; @@ -413,12 +419,13 @@ pub fn from_protobuf_value( let full_name = msg_desc.clone().full_name().to_string(); // Decode the payload based on the `msg_desc` - let decoded_value = DynamicMessage::decode(msg_desc, payload.as_ref()).unwrap(); + let decoded_value = DynamicMessage::decode(msg_desc, payload).unwrap(); let decoded_value = from_protobuf_value( field_desc, &Value::Message(decoded_value), descriptor_pool, )? + .to_owned_datum() .unwrap(); // Extract the struct value @@ -447,7 +454,9 @@ pub fn from_protobuf_value( } // use default value if dyn_msg doesn't has this field let value = dyn_msg.get_field(&field_desc); - rw_values.push(from_protobuf_value(&field_desc, &value, descriptor_pool)?); + rw_values.push( + from_protobuf_value(&field_desc, &value, descriptor_pool)?.to_owned_datum(), + ); } ScalarImpl::Struct(StructValue::new(rw_values)) } @@ -461,14 +470,14 @@ pub fn from_protobuf_value( } ScalarImpl::List(ListValue::new(builder.finish())) } - Value::Bytes(value) => ScalarImpl::Bytea(value.to_vec().into_boxed_slice()), + Value::Bytes(value) => borrowed!(&**value), _ => { return Err(AccessError::UnsupportedType { ty: format!("{kind:?}"), }); } }; - Ok(Some(v)) + Ok(Some(v).into()) } /// Maps protobuf type to RW type. @@ -579,11 +588,11 @@ pub(crate) fn resolve_pb_header(payload: &[u8]) -> ConnectorResult<&[u8]> { #[cfg(test)] mod test { - use std::collections::HashMap; use std::path::PathBuf; use prost::Message; - use risingwave_common::types::{DataType, ListValue, StructType}; + use risingwave_common::types::StructType; + use risingwave_connector_codec::decoder::AccessExt; use risingwave_pb::catalog::StreamSourceInfo; use risingwave_pb::data::data_type::PbTypeName; use risingwave_pb::plan_common::{PbEncodeType, PbFormatType}; @@ -592,7 +601,6 @@ mod test { use super::*; use crate::parser::protobuf::recursive::all_types::{EnumType, ExampleOneof, NestedMessage}; use crate::parser::protobuf::recursive::AllTypes; - use crate::parser::unified::Access; use crate::parser::SpecificParserConfig; fn schema_dir() -> String { @@ -624,7 +632,7 @@ mod test { row_encode: PbEncodeType::Protobuf.into(), ..Default::default() }; - let parser_config = SpecificParserConfig::new(&info, &HashMap::new())?; + let parser_config = SpecificParserConfig::new(&info, &Default::default())?; let conf = ProtobufParserConfig::new(parser_config.encoding_config).await?; let value = DynamicMessage::decode(conf.message_descriptor, PRE_GEN_PROTO_DATA).unwrap(); @@ -669,7 +677,7 @@ mod test { row_encode: PbEncodeType::Protobuf.into(), ..Default::default() }; - let parser_config = SpecificParserConfig::new(&info, &HashMap::new())?; + let parser_config = SpecificParserConfig::new(&info, &Default::default())?; let conf = ProtobufParserConfig::new(parser_config.encoding_config).await?; let columns = conf.map_to_columns().unwrap(); @@ -718,7 +726,7 @@ mod test { row_encode: PbEncodeType::Protobuf.into(), ..Default::default() }; - let parser_config = SpecificParserConfig::new(&info, &HashMap::new()).unwrap(); + let parser_config = SpecificParserConfig::new(&info, &Default::default()).unwrap(); let conf = ProtobufParserConfig::new(parser_config.encoding_config) .await .unwrap(); @@ -746,7 +754,7 @@ mod test { row_encode: PbEncodeType::Protobuf.into(), ..Default::default() }; - let parser_config = SpecificParserConfig::new(&info, &HashMap::new()).unwrap(); + let parser_config = SpecificParserConfig::new(&info, &Default::default()).unwrap(); ProtobufParserConfig::new(parser_config.encoding_config) .await @@ -896,7 +904,8 @@ mod test { } fn pb_eq(a: &ProtobufAccess, field_name: &str, value: ScalarImpl) { - let d = a.access(&[field_name], None).unwrap().unwrap(); + let dummy_type = DataType::Varchar; + let d = a.access_owned(&[field_name], &dummy_type).unwrap().unwrap(); assert_eq!(d, value, "field: {} value: {:?}", field_name, d); } @@ -965,7 +974,9 @@ mod test { let field = value.fields().next().unwrap().0; if let Some(ret) = - from_protobuf_value(&field, &Value::Message(value), &conf.descriptor_pool).unwrap() + from_protobuf_value(&field, &Value::Message(value), &conf.descriptor_pool) + .unwrap() + .to_owned_datum() { println!("Decoded Value for ANY_GEN_PROTO_DATA: {:#?}", ret); println!("---------------------------"); @@ -1026,7 +1037,9 @@ mod test { let field = value.fields().next().unwrap().0; if let Some(ret) = - from_protobuf_value(&field, &Value::Message(value), &conf.descriptor_pool).unwrap() + from_protobuf_value(&field, &Value::Message(value), &conf.descriptor_pool) + .unwrap() + .to_owned_datum() { println!("Decoded Value for ANY_GEN_PROTO_DATA: {:#?}", ret); println!("---------------------------"); @@ -1098,7 +1111,9 @@ mod test { let field = value.fields().next().unwrap().0; if let Some(ret) = - from_protobuf_value(&field, &Value::Message(value), &conf.descriptor_pool).unwrap() + from_protobuf_value(&field, &Value::Message(value), &conf.descriptor_pool) + .unwrap() + .to_owned_datum() { println!("Decoded Value for ANY_RECURSIVE_GEN_PROTO_DATA: {:#?}", ret); println!("---------------------------"); diff --git a/src/connector/src/parser/scalar_adapter.rs b/src/connector/src/parser/scalar_adapter.rs new file mode 100644 index 0000000000000..0f5d2d6d6d935 --- /dev/null +++ b/src/connector/src/parser/scalar_adapter.rs @@ -0,0 +1,379 @@ +// Copyright 2024 RisingWave Labs +// +// 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::str::FromStr; + +use anyhow::anyhow; +use bytes::BytesMut; +use pg_bigdecimal::PgNumeric; +use risingwave_common::types::{DataType, Decimal, Int256, ListValue, ScalarImpl, ScalarRefImpl}; +use thiserror_ext::AsReport; +use tokio_postgres::types::{to_sql_checked, FromSql, IsNull, Kind, ToSql, Type}; + +use crate::error::ConnectorResult; + +#[derive(Clone, Debug)] +pub struct EnumString(pub String); + +impl<'a> FromSql<'a> for EnumString { + fn from_sql( + _ty: &Type, + raw: &'a [u8], + ) -> Result> { + Ok(EnumString(String::from_utf8_lossy(raw).into_owned())) + } + + fn accepts(ty: &Type) -> bool { + matches!(ty.kind(), Kind::Enum(_)) + } +} + +impl ToSql for EnumString { + to_sql_checked!(); + + fn to_sql( + &self, + ty: &Type, + out: &mut BytesMut, + ) -> Result> + where + Self: Sized, + { + match ty.kind() { + Kind::Enum(e) => { + if e.contains(&self.0) { + out.extend_from_slice(self.0.as_bytes()); + Ok(IsNull::No) + } else { + Err(format!( + "EnumString value {} is not in the enum type {:?}", + self.0, e + ) + .into()) + } + } + _ => Err("EnumString can only be used with ENUM types".into()), + } + } + + fn accepts(ty: &Type) -> bool { + matches!(ty.kind(), Kind::Enum(_)) + } +} + +/// Adapter for `ScalarImpl` to Postgres data type, +/// which can be used to encode/decode to/from Postgres value. +#[derive(Debug)] +pub(crate) enum ScalarAdapter { + Builtin(ScalarImpl), + Uuid(uuid::Uuid), + // Currently in order to handle the decimal beyond RustDecimal, + // we use the PgNumeric type to convert the decimal to a string/decimal/rw_int256. + Numeric(PgNumeric), + Enum(EnumString), + NumericList(Vec>), + EnumList(Vec>), + // UuidList is covered by List, while NumericList and EnumList are special cases. + // Note: The IntervalList is not supported. + List(Vec>), +} + +impl ToSql for ScalarAdapter { + to_sql_checked!(); + + fn to_sql( + &self, + ty: &Type, + out: &mut bytes::BytesMut, + ) -> Result> { + match self { + ScalarAdapter::Builtin(v) => v.to_sql(ty, out), + ScalarAdapter::Uuid(v) => v.to_sql(ty, out), + ScalarAdapter::Numeric(v) => v.to_sql(ty, out), + ScalarAdapter::Enum(v) => v.to_sql(ty, out), + ScalarAdapter::NumericList(v) => v.to_sql(ty, out), + ScalarAdapter::EnumList(v) => v.to_sql(ty, out), + ScalarAdapter::List(v) => v.to_sql(ty, out), + } + } + + fn accepts(_ty: &Type) -> bool { + true + } +} + +/// convert from Postgres uuid, numeric and enum to `ScalarAdapter` +impl<'a> FromSql<'a> for ScalarAdapter { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + match ty.kind() { + Kind::Simple => match *ty { + Type::UUID => Ok(ScalarAdapter::Uuid(uuid::Uuid::from_sql(ty, raw)?)), + // In order to cover the decimal beyond RustDecimal(only 28 digits are supported), + // we use the PgNumeric to handle decimal from postgres. + Type::NUMERIC => Ok(ScalarAdapter::Numeric(PgNumeric::from_sql(ty, raw)?)), + _ => Ok(ScalarAdapter::Builtin(ScalarImpl::from_sql(ty, raw)?)), + }, + Kind::Enum(_) => Ok(ScalarAdapter::Enum(EnumString::from_sql(ty, raw)?)), + Kind::Array(Type::NUMERIC) => { + Ok(ScalarAdapter::NumericList(FromSql::from_sql(ty, raw)?)) + } + Kind::Array(inner_type) if let Kind::Enum(_) = inner_type.kind() => { + Ok(ScalarAdapter::EnumList(FromSql::from_sql(ty, raw)?)) + } + Kind::Array(_) => Ok(ScalarAdapter::List(FromSql::from_sql(ty, raw)?)), + _ => Err(anyhow!("failed to convert type {:?} to ScalarAdapter", ty).into()), + } + } + + fn accepts(ty: &Type) -> bool { + match ty.kind() { + Kind::Simple => { + matches!(ty, &Type::UUID | &Type::NUMERIC) || ::accepts(ty) + } + Kind::Enum(_) => true, + Kind::Array(inner_type) => ::accepts(inner_type), + _ => false, + } + } +} + +impl ScalarAdapter { + pub fn name(&self) -> &'static str { + match self { + ScalarAdapter::Builtin(_) => "Builtin", + ScalarAdapter::Uuid(_) => "Uuid", + ScalarAdapter::Numeric(_) => "Numeric", + ScalarAdapter::Enum(_) => "Enum", + ScalarAdapter::EnumList(_) => "EnumList", + ScalarAdapter::NumericList(_) => "NumericList", + ScalarAdapter::List(_) => "List", + } + } + + /// convert `ScalarRefImpl` to `ScalarAdapter` so that we can correctly encode to postgres value + pub(crate) fn from_scalar( + scalar: ScalarRefImpl<'_>, + ty: &Type, + ) -> ConnectorResult { + Ok(match (scalar, ty, ty.kind()) { + (ScalarRefImpl::Utf8(s), &Type::UUID, _) => ScalarAdapter::Uuid(s.parse()?), + (ScalarRefImpl::Utf8(s), &Type::NUMERIC, _) => { + ScalarAdapter::Numeric(string_to_pg_numeric(s)) + } + (ScalarRefImpl::Int256(s), &Type::NUMERIC, _) => { + ScalarAdapter::Numeric(string_to_pg_numeric(&s.to_string())) + } + (ScalarRefImpl::Utf8(s), _, Kind::Enum(_)) => { + ScalarAdapter::Enum(EnumString(s.to_owned())) + } + (ScalarRefImpl::List(list), &Type::NUMERIC_ARRAY, _) => { + let mut vec = vec![]; + for datum in list.iter() { + vec.push(match datum { + Some(ScalarRefImpl::Int256(s)) => Some(string_to_pg_numeric(&s.to_string())), + Some(ScalarRefImpl::Decimal(s)) => Some(rw_numeric_to_pg_numeric(s)), + Some(ScalarRefImpl::Utf8(s)) => Some(string_to_pg_numeric(s)), + None => None, + _ => { + unreachable!("Only rw-numeric[], rw_int256[] and varchar[] are supported to convert to pg-numeric[]"); + } + }) + } + ScalarAdapter::NumericList(vec) + } + (ScalarRefImpl::List(list), _, Kind::Array(inner_type)) => match inner_type.kind() { + Kind::Enum(_) => { + let mut vec = vec![]; + for datum in list.iter() { + vec.push(match datum { + Some(ScalarRefImpl::Utf8(s)) => Some(EnumString(s.to_owned())), + _ => unreachable!( + "Only non-null varchar[] is supported to convert to enum[]" + ), + }) + } + ScalarAdapter::EnumList(vec) + } + _ => { + let mut vec = vec![]; + for datum in list.iter() { + vec.push( + datum + .map(|s| ScalarAdapter::from_scalar(s, inner_type)) + .transpose()?, + ); + } + ScalarAdapter::List(vec) + } + }, + _ => ScalarAdapter::Builtin(scalar.into_scalar_impl()), + }) + } + + pub fn into_scalar(self, ty: &DataType) -> Option { + match (self, &ty) { + (ScalarAdapter::Builtin(scalar), _) => Some(scalar), + (ScalarAdapter::Uuid(uuid), &DataType::Varchar) => { + Some(ScalarImpl::from(uuid.to_string())) + } + (ScalarAdapter::Numeric(numeric), &DataType::Varchar) => { + Some(ScalarImpl::from(pg_numeric_to_string(&numeric))) + } + (ScalarAdapter::Numeric(numeric), &DataType::Int256) => { + pg_numeric_to_rw_int256(&numeric) + } + (ScalarAdapter::Numeric(numeric), &DataType::Decimal) => { + pg_numeric_to_rw_numeric(&numeric) + } + (ScalarAdapter::Enum(EnumString(s)), &DataType::Varchar) => Some(ScalarImpl::from(s)), + (ScalarAdapter::NumericList(vec), &DataType::List(dtype)) => { + let mut builder = dtype.create_array_builder(0); + for val in vec { + let scalar = match (val, &dtype) { + // A numeric array contains special values like NaN, Inf, -Inf, which are not supported in Debezium, + // when we encounter these special values, we fallback the array to NULL, returning None directly. + (Some(numeric), box DataType::Varchar) => { + if pg_numeric_is_special(&numeric) { + return None; + } else { + ScalarAdapter::Numeric(numeric).into_scalar(dtype) + } + } + (Some(numeric), box DataType::Int256 | box DataType::Decimal) => { + if pg_numeric_is_special(&numeric) { + return None; + } else { + // A PgNumeric can sometimes exceeds the range of Int256 and RwNumeric. + // In our json parsing, we fallback the array to NULL in this case. + // Here we keep the behavior consistent and return None directly. + match ScalarAdapter::Numeric(numeric).into_scalar(dtype) { + Some(scalar) => Some(scalar), + None => { + return None; + } + } + } + } + (Some(_), _) => unreachable!( + "Only rw-numeric[], rw_int256[] and varchar[] are supported to convert to pg-numeric[]" + ), + // This item is NULL, continue to handle next item. + (None, _) => None, + }; + builder.append(scalar); + } + Some(ScalarImpl::from(ListValue::new(builder.finish()))) + } + (ScalarAdapter::EnumList(vec), &DataType::List(dtype)) => { + let mut builder = dtype.create_array_builder(0); + for val in vec { + match val { + Some(EnumString(s)) => { + builder.append(Some(ScalarImpl::from(s))); + } + None => { + return None; + } + } + } + Some(ScalarImpl::from(ListValue::new(builder.finish()))) + } + (ScalarAdapter::List(vec), &DataType::List(dtype)) => { + // Due to https://github.com/risingwavelabs/risingwave/issues/16882, INTERVAL_ARRAY is not supported in Debezium, so we keep backfilling and CDC consistent. + if matches!(**dtype, DataType::Interval) { + return None; + } + let mut builder = dtype.create_array_builder(0); + for val in vec { + builder.append(val.and_then(|v| v.into_scalar(dtype))); + } + Some(ScalarImpl::from(ListValue::new(builder.finish()))) + } + (scaler, ty) => { + tracing::error!( + adapter = scaler.name(), + rw_type = ty.pg_name(), + "failed to convert from ScalarAdapter: invalid conversion" + ); + None + } + } + } +} + +fn pg_numeric_is_special(val: &PgNumeric) -> bool { + matches!( + val, + PgNumeric::NegativeInf | PgNumeric::PositiveInf | PgNumeric::NaN + ) +} + +fn pg_numeric_to_rw_int256(val: &PgNumeric) -> Option { + match Int256::from_str(pg_numeric_to_string(val).as_str()) { + Ok(num) => Some(ScalarImpl::from(num)), + Err(err) => { + tracing::error!(error = %err.as_report(), "failed to convert PgNumeric to Int256"); + None + } + } +} + +fn pg_numeric_to_rw_numeric(val: &PgNumeric) -> Option { + match val { + PgNumeric::NegativeInf => Some(ScalarImpl::from(Decimal::NegativeInf)), + PgNumeric::Normalized(big_decimal) => { + match Decimal::from_str(big_decimal.to_string().as_str()) { + Ok(num) => Some(ScalarImpl::from(num)), + Err(err) => { + tracing::error!(error = %err.as_report(), "parse pg-numeric as rw-numeric failed (likely out-of-range"); + None + } + } + } + PgNumeric::PositiveInf => Some(ScalarImpl::from(Decimal::PositiveInf)), + PgNumeric::NaN => Some(ScalarImpl::from(Decimal::NaN)), + } +} + +fn pg_numeric_to_string(val: &PgNumeric) -> String { + // TODO(kexiang): NEGATIVE_INFINITY -> -Infinity, POSITIVE_INFINITY -> Infinity, NAN -> NaN + // The current implementation is to ensure consistency with the behavior of cdc event parsor. + match val { + PgNumeric::NegativeInf => String::from("NEGATIVE_INFINITY"), + PgNumeric::Normalized(big_decimal) => big_decimal.to_string(), + PgNumeric::PositiveInf => String::from("POSITIVE_INFINITY"), + PgNumeric::NaN => String::from("NAN"), + } +} + +fn string_to_pg_numeric(s: &str) -> PgNumeric { + match s { + "NEGATIVE_INFINITY" => PgNumeric::NegativeInf, + "POSITIVE_INFINITY" => PgNumeric::PositiveInf, + "NAN" => PgNumeric::NaN, + _ => PgNumeric::Normalized(s.parse().unwrap()), + } +} + +fn rw_numeric_to_pg_numeric(val: Decimal) -> PgNumeric { + match val { + Decimal::NegativeInf => PgNumeric::NegativeInf, + Decimal::Normalized(inner) => PgNumeric::Normalized(inner.to_string().parse().unwrap()), + Decimal::PositiveInf => PgNumeric::PositiveInf, + Decimal::NaN => PgNumeric::NaN, + } +} diff --git a/src/connector/src/parser/unified/bytes.rs b/src/connector/src/parser/unified/bytes.rs index ff47424d60acf..2f8f27124a307 100644 --- a/src/connector/src/parser/unified/bytes.rs +++ b/src/connector/src/parser/unified/bytes.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use risingwave_common::types::{DataType, ScalarImpl}; +use risingwave_common::types::{DataType, DatumCow, ScalarRefImpl}; use super::{Access, AccessError, AccessResult}; @@ -29,14 +29,16 @@ impl<'a> BytesAccess<'a> { } } -impl<'a> Access for BytesAccess<'a> { +impl Access for BytesAccess<'_> { /// path is empty currently, `type_expected` should be `Bytea` - fn access(&self, path: &[&str], type_expected: Option<&DataType>) -> AccessResult { - if let DataType::Bytea = type_expected.unwrap() { + fn access<'a>(&'a self, path: &[&str], type_expected: &DataType) -> AccessResult> { + if let DataType::Bytea = type_expected { if self.column_name.is_none() || (path.len() == 1 && self.column_name.as_ref().unwrap() == path[0]) { - return Ok(Some(ScalarImpl::Bytea(Box::from(self.bytes.as_slice())))); + return Ok(DatumCow::Borrowed(Some(ScalarRefImpl::Bytea( + self.bytes.as_slice(), + )))); } return Err(AccessError::Undefined { name: path[0].to_string(), diff --git a/src/connector/src/parser/unified/debezium.rs b/src/connector/src/parser/unified/debezium.rs index 589fdfc8cfc45..d90463698577d 100644 --- a/src/connector/src/parser/unified/debezium.rs +++ b/src/connector/src/parser/unified/debezium.rs @@ -12,12 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. -use risingwave_common::types::{DataType, Datum, ScalarImpl}; +use risingwave_common::types::{ + DataType, Datum, DatumCow, Scalar, ScalarImpl, ScalarRefImpl, Timestamptz, ToDatumRef, +}; +use risingwave_connector_codec::decoder::AccessExt; +use risingwave_pb::plan_common::additional_column::ColumnType; use super::{Access, AccessError, AccessResult, ChangeEvent, ChangeEventOperation}; use crate::parser::TransactionControl; use crate::source::{ConnectorProperties, SourceColumnDesc}; +// Example of Debezium JSON value: +// { +// "payload": +// { +// "before": null, +// "after": +// { +// "O_ORDERKEY": 5, +// "O_CUSTKEY": 44485, +// "O_ORDERSTATUS": "F", +// "O_TOTALPRICE": "144659.20", +// "O_ORDERDATE": "1994-07-30" +// }, +// "source": +// { +// "version": "1.9.7.Final", +// "connector": "mysql", +// "name": "RW_CDC_1002", +// "ts_ms": 1695277757000, +// "db": "mydb", +// "sequence": null, +// "table": "orders", +// "server_id": 0, +// "gtid": null, +// "file": "binlog.000008", +// "pos": 3693, +// "row": 0, +// }, +// "op": "r", +// "ts_ms": 1695277757017, +// "transaction": null +// } +// } pub struct DebeziumChangeEvent { value_accessor: Option, key_accessor: Option, @@ -26,6 +63,14 @@ pub struct DebeziumChangeEvent { const BEFORE: &str = "before"; const AFTER: &str = "after"; + +const SOURCE: &str = "source"; +const SOURCE_TS_MS: &str = "ts_ms"; +const SOURCE_DB: &str = "db"; +const SOURCE_SCHEMA: &str = "schema"; +const SOURCE_TABLE: &str = "table"; +const SOURCE_COLLECTION: &str = "collection"; + const OP: &str = "op"; pub const TRANSACTION_STATUS: &str = "status"; pub const TRANSACTION_ID: &str = "id"; @@ -42,20 +87,26 @@ pub fn parse_transaction_meta( accessor: &impl Access, connector_props: &ConnectorProperties, ) -> AccessResult { - if let (Some(ScalarImpl::Utf8(status)), Some(ScalarImpl::Utf8(id))) = ( - accessor.access(&[TRANSACTION_STATUS], Some(&DataType::Varchar))?, - accessor.access(&[TRANSACTION_ID], Some(&DataType::Varchar))?, + if let (Some(ScalarRefImpl::Utf8(status)), Some(ScalarRefImpl::Utf8(id))) = ( + accessor + .access(&[TRANSACTION_STATUS], &DataType::Varchar)? + .to_datum_ref(), + accessor + .access(&[TRANSACTION_ID], &DataType::Varchar)? + .to_datum_ref(), ) { // The id field has different meanings for different databases: // PG: txID:LSN // MySQL: source_id:transaction_id (e.g. 3E11FA47-71CA-11E1-9E33-C80AA9429562:23) - match status.as_ref() { + match status { DEBEZIUM_TRANSACTION_STATUS_BEGIN => match *connector_props { ConnectorProperties::PostgresCdc(_) => { let (tx_id, _) = id.split_once(':').unwrap(); return Ok(TransactionControl::Begin { id: tx_id.into() }); } - ConnectorProperties::MysqlCdc(_) => return Ok(TransactionControl::Begin { id }), + ConnectorProperties::MysqlCdc(_) => { + return Ok(TransactionControl::Begin { id: id.into() }) + } _ => {} }, DEBEZIUM_TRANSACTION_STATUS_COMMIT => match *connector_props { @@ -63,7 +114,9 @@ pub fn parse_transaction_meta( let (tx_id, _) = id.split_once(':').unwrap(); return Ok(TransactionControl::Commit { id: tx_id.into() }); } - ConnectorProperties::MysqlCdc(_) => return Ok(TransactionControl::Commit { id }), + ConnectorProperties::MysqlCdc(_) => { + return Ok(TransactionControl::Commit { id: id.into() }) + } _ => {} }, _ => {} @@ -80,14 +133,6 @@ impl DebeziumChangeEvent where A: Access, { - pub fn with_value(value_accessor: A) -> Self { - Self::new(None, Some(value_accessor)) - } - - pub fn with_key(key_accessor: A) -> Self { - Self::new(Some(key_accessor), None) - } - /// Panic: one of the `key_accessor` or `value_accessor` must be provided. pub fn new(key_accessor: Option, value_accessor: Option) -> Self { assert!(key_accessor.is_some() || value_accessor.is_some()); @@ -126,7 +171,7 @@ impl ChangeEvent for DebeziumChangeEvent where A: Access, { - fn access_field(&self, desc: &SourceColumnDesc) -> super::AccessResult { + fn access_field(&self, desc: &SourceColumnDesc) -> super::AccessResult> { match self.op()? { ChangeEventOperation::Delete => { // For delete events of MongoDB, the "before" and "after" field both are null in the value, @@ -136,32 +181,80 @@ where .key_accessor .as_ref() .expect("key_accessor must be provided for delete operation") - .access(&[&desc.name], Some(&desc.data_type)); + .access(&[&desc.name], &desc.data_type); } if let Some(va) = self.value_accessor.as_ref() { - va.access(&[BEFORE, &desc.name], Some(&desc.data_type)) + va.access(&[BEFORE, &desc.name], &desc.data_type) } else { self.key_accessor .as_ref() .unwrap() - .access(&[&desc.name], Some(&desc.data_type)) + .access(&[&desc.name], &desc.data_type) } } // value should not be None. - ChangeEventOperation::Upsert => self - .value_accessor - .as_ref() - .unwrap() - .access(&[AFTER, &desc.name], Some(&desc.data_type)), + ChangeEventOperation::Upsert => { + // For upsert operation, if desc is an additional column, access field in the `SOURCE` field. + desc.additional_column.column_type.as_ref().map_or_else( + || { + self.value_accessor + .as_ref() + .expect("value_accessor must be provided for upsert operation") + .access(&[AFTER, &desc.name], &desc.data_type) + }, + |additional_column_type| { + match *additional_column_type { + ColumnType::Timestamp(_) => { + // access payload.source.ts_ms + let ts_ms = self + .value_accessor + .as_ref() + .expect("value_accessor must be provided for upsert operation") + .access_owned(&[SOURCE, SOURCE_TS_MS], &DataType::Int64)?; + Ok(DatumCow::Owned(ts_ms.map(|scalar| { + Timestamptz::from_millis(scalar.into_int64()) + .expect("source.ts_ms must in millisecond") + .to_scalar_value() + }))) + } + ColumnType::DatabaseName(_) => self + .value_accessor + .as_ref() + .expect("value_accessor must be provided for upsert operation") + .access(&[SOURCE, SOURCE_DB], &desc.data_type), + ColumnType::SchemaName(_) => self + .value_accessor + .as_ref() + .expect("value_accessor must be provided for upsert operation") + .access(&[SOURCE, SOURCE_SCHEMA], &desc.data_type), + ColumnType::TableName(_) => self + .value_accessor + .as_ref() + .expect("value_accessor must be provided for upsert operation") + .access(&[SOURCE, SOURCE_TABLE], &desc.data_type), + ColumnType::CollectionName(_) => self + .value_accessor + .as_ref() + .expect("value_accessor must be provided for upsert operation") + .access(&[SOURCE, SOURCE_COLLECTION], &desc.data_type), + _ => Err(AccessError::UnsupportedAdditionalColumn { + name: desc.name.clone(), + }), + } + }, + ) + } } } fn op(&self) -> Result { if let Some(accessor) = &self.value_accessor { - if let Some(ScalarImpl::Utf8(op)) = accessor.access(&[OP], Some(&DataType::Varchar))? { - match op.as_ref() { + if let Some(ScalarRefImpl::Utf8(op)) = + accessor.access(&[OP], &DataType::Varchar)?.to_datum_ref() + { + match op { DEBEZIUM_READ_OP | DEBEZIUM_CREATE_OP | DEBEZIUM_UPDATE_OP => { return Ok(ChangeEventOperation::Upsert) } @@ -247,15 +340,12 @@ impl Access for MongoJsonAccess where A: Access, { - fn access(&self, path: &[&str], type_expected: Option<&DataType>) -> super::AccessResult { + fn access<'a>(&'a self, path: &[&str], type_expected: &DataType) -> AccessResult> { match path { ["after" | "before", "_id"] => { - let payload = self.access(&[path[0]], Some(&DataType::Jsonb))?; + let payload = self.access_owned(&[path[0]], &DataType::Jsonb)?; if let Some(ScalarImpl::Jsonb(bson_doc)) = payload { - Ok(extract_bson_id( - type_expected.unwrap_or(&DataType::Jsonb), - &bson_doc.take(), - )?) + Ok(extract_bson_id(type_expected, &bson_doc.take())?.into()) } else { // fail to extract the "_id" field from the message payload Err(AccessError::Undefined { @@ -264,19 +354,16 @@ where })? } } - ["after" | "before", "payload"] => self.access(&[path[0]], Some(&DataType::Jsonb)), + ["after" | "before", "payload"] => self.access(&[path[0]], &DataType::Jsonb), // To handle a DELETE message, we need to extract the "_id" field from the message key, because it is not in the payload. // In addition, the "_id" field is named as "id" in the key. An example of message key: // {"schema":null,"payload":{"id":"{\"$oid\": \"65bc9fb6c485f419a7a877fe\"}"}} ["_id"] => { let ret = self.accessor.access(path, type_expected); if matches!(ret, Err(AccessError::Undefined { .. })) { - let id_bson = self.accessor.access(&["id"], Some(&DataType::Jsonb))?; + let id_bson = self.accessor.access_owned(&["id"], &DataType::Jsonb)?; if let Some(ScalarImpl::Jsonb(bson_doc)) = id_bson { - Ok(extract_bson_id( - type_expected.unwrap_or(&DataType::Jsonb), - &bson_doc.take(), - )?) + Ok(extract_bson_id(type_expected, &bson_doc.take())?.into()) } else { // fail to extract the "_id" field from the message key Err(AccessError::Undefined { diff --git a/src/connector/src/parser/unified/json.rs b/src/connector/src/parser/unified/json.rs index d9deb73d582d9..e4a229bb61b98 100644 --- a/src/connector/src/parser/unified/json.rs +++ b/src/connector/src/parser/unified/json.rs @@ -23,8 +23,10 @@ use risingwave_common::cast::{i64_to_timestamp, i64_to_timestamptz, str_to_bytea use risingwave_common::log::LogSuppresser; use risingwave_common::types::{ DataType, Date, Decimal, Int256, Interval, JsonbVal, ScalarImpl, Time, Timestamp, Timestamptz, + ToOwnedDatum, }; use risingwave_common::util::iter_util::ZipEqFast; +use risingwave_connector_codec::decoder::utils::extract_decimal; use simd_json::prelude::{ TypedValue, ValueAsContainer, ValueAsScalar, ValueObjectAccess, ValueTryAsScalar, }; @@ -33,7 +35,7 @@ use thiserror_ext::AsReport; use super::{Access, AccessError, AccessResult}; use crate::parser::common::json_object_get_case_insensitive; -use crate::parser::unified::avro::extract_decimal; +use crate::parser::DatumCow; use crate::schema::{bail_invalid_option_error, InvalidOptionError}; #[derive(Clone, Debug)] @@ -199,11 +201,11 @@ impl JsonParseOptions { } } - pub fn parse( + pub fn parse<'a>( &self, - value: &BorrowedValue<'_>, - type_expected: Option<&DataType>, - ) -> AccessResult { + value: &'a BorrowedValue<'a>, + type_expected: &DataType, + ) -> AccessResult> { let create_error = || AccessError::TypeError { expected: format!("{:?}", type_expected), got: value.value_type().to_string(), @@ -211,12 +213,12 @@ impl JsonParseOptions { }; let v: ScalarImpl = match (type_expected, value.value_type()) { - (_, ValueType::Null) => return Ok(None), + (_, ValueType::Null) => return Ok(DatumCow::NULL), // ---- Boolean ----- - (Some(DataType::Boolean) | None, ValueType::Bool) => value.as_bool().unwrap().into(), + (DataType::Boolean, ValueType::Bool) => value.as_bool().unwrap().into(), ( - Some(DataType::Boolean), + DataType::Boolean, ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128, ) if matches!(self.boolean_handling, BooleanHandling::Relax { .. }) && matches!(value.as_i64(), Some(0i64) | Some(1i64)) => @@ -224,7 +226,7 @@ impl JsonParseOptions { (value.as_i64() == Some(1i64)).into() } - (Some(DataType::Boolean), ValueType::String) + (DataType::Boolean, ValueType::String) if matches!( self.boolean_handling, BooleanHandling::Relax { @@ -256,11 +258,11 @@ impl JsonParseOptions { } // ---- Int16 ----- ( - Some(DataType::Int16), + DataType::Int16, ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128, ) => value.try_as_i16().map_err(|_| create_error())?.into(), - (Some(DataType::Int16), ValueType::String) + (DataType::Int16, ValueType::String) if matches!( self.numeric_handling, NumericHandling::Relax { @@ -277,11 +279,11 @@ impl JsonParseOptions { } // ---- Int32 ----- ( - Some(DataType::Int32), + DataType::Int32, ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128, ) => value.try_as_i32().map_err(|_| create_error())?.into(), - (Some(DataType::Int32), ValueType::String) + (DataType::Int32, ValueType::String) if matches!( self.numeric_handling, NumericHandling::Relax { @@ -297,15 +299,12 @@ impl JsonParseOptions { .into() } // ---- Int64 ----- - (None, ValueType::I64 | ValueType::U64) => { - value.try_as_i64().map_err(|_| create_error())?.into() - } ( - Some(DataType::Int64), + DataType::Int64, ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128, ) => value.try_as_i64().map_err(|_| create_error())?.into(), - (Some(DataType::Int64), ValueType::String) + (DataType::Int64, ValueType::String) if matches!( self.numeric_handling, NumericHandling::Relax { @@ -322,12 +321,12 @@ impl JsonParseOptions { } // ---- Float32 ----- ( - Some(DataType::Float32), + DataType::Float32, ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128, ) if matches!(self.numeric_handling, NumericHandling::Relax { .. }) => { (value.try_as_i64().map_err(|_| create_error())? as f32).into() } - (Some(DataType::Float32), ValueType::String) + (DataType::Float32, ValueType::String) if matches!( self.numeric_handling, NumericHandling::Relax { @@ -342,17 +341,17 @@ impl JsonParseOptions { .map_err(|_| create_error())? .into() } - (Some(DataType::Float32), ValueType::F64) => { + (DataType::Float32, ValueType::F64) => { value.try_as_f32().map_err(|_| create_error())?.into() } // ---- Float64 ----- ( - Some(DataType::Float64), + DataType::Float64, ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128, ) if matches!(self.numeric_handling, NumericHandling::Relax { .. }) => { (value.try_as_i64().map_err(|_| create_error())? as f64).into() } - (Some(DataType::Float64), ValueType::String) + (DataType::Float64, ValueType::String) if matches!( self.numeric_handling, NumericHandling::Relax { @@ -367,29 +366,37 @@ impl JsonParseOptions { .map_err(|_| create_error())? .into() } - (Some(DataType::Float64) | None, ValueType::F64) => { + (DataType::Float64, ValueType::F64) => { value.try_as_f64().map_err(|_| create_error())?.into() } // ---- Decimal ----- - (Some(DataType::Decimal) | None, ValueType::I128 | ValueType::U128) => { + (DataType::Decimal, ValueType::I128 | ValueType::U128) => { Decimal::from_str(&value.try_as_i128().map_err(|_| create_error())?.to_string()) .map_err(|_| create_error())? .into() } - (Some(DataType::Decimal), ValueType::I64 | ValueType::U64) => { + (DataType::Decimal, ValueType::I64 | ValueType::U64) => { Decimal::from(value.try_as_i64().map_err(|_| create_error())?).into() } - (Some(DataType::Decimal), ValueType::F64) => { + (DataType::Decimal, ValueType::F64) => { Decimal::try_from(value.try_as_f64().map_err(|_| create_error())?) .map_err(|_| create_error())? .into() } - - (Some(DataType::Decimal), ValueType::String) => ScalarImpl::Decimal( - Decimal::from_str(value.as_str().unwrap()).map_err(|_err| create_error())?, - ), - (Some(DataType::Decimal), ValueType::Object) => { + (DataType::Decimal, ValueType::String) => { + let str = value.as_str().unwrap(); + // the following values are special string generated by Debezium and should be handled separately + match str { + "NAN" => ScalarImpl::Decimal(Decimal::NaN), + "POSITIVE_INFINITY" => ScalarImpl::Decimal(Decimal::PositiveInf), + "NEGATIVE_INFINITY" => ScalarImpl::Decimal(Decimal::NegativeInf), + _ => { + ScalarImpl::Decimal(Decimal::from_str(str).map_err(|_err| create_error())?) + } + } + } + (DataType::Decimal, ValueType::Object) => { // ref https://github.com/risingwavelabs/risingwave/issues/10628 // handle debezium json (variable scale): {"scale": int, "value": bytes} let scale = value @@ -412,21 +419,23 @@ impl JsonParseOptions { } // ---- Date ----- ( - Some(DataType::Date), + DataType::Date, ValueType::I64 | ValueType::I128 | ValueType::U64 | ValueType::U128, ) => Date::with_days_since_unix_epoch(value.try_as_i32().map_err(|_| create_error())?) .map_err(|_| create_error())? .into(), - (Some(DataType::Date), ValueType::String) => value + (DataType::Date, ValueType::String) => value .as_str() .unwrap() .parse::() .map_err(|_| create_error())? .into(), // ---- Varchar ----- - (Some(DataType::Varchar) | None, ValueType::String) => value.as_str().unwrap().into(), + (DataType::Varchar, ValueType::String) => { + return Ok(DatumCow::Borrowed(Some(value.as_str().unwrap().into()))) + } ( - Some(DataType::Varchar), + DataType::Varchar, ValueType::Bool | ValueType::I64 | ValueType::I128 @@ -437,7 +446,7 @@ impl JsonParseOptions { value.to_string().into() } ( - Some(DataType::Varchar), + DataType::Varchar, ValueType::Bool | ValueType::I64 | ValueType::I128 @@ -450,14 +459,14 @@ impl JsonParseOptions { value.to_string().into() } // ---- Time ----- - (Some(DataType::Time), ValueType::String) => value + (DataType::Time, ValueType::String) => value .as_str() .unwrap() .parse:: ChangeEvent for (ChangeEventOperation, A) where A: Access, { - fn op(&self) -> std::result::Result { + fn op(&self) -> AccessResult { Ok(self.0) } - fn access_field(&self, desc: &SourceColumnDesc) -> AccessResult { - self.1.access(&[desc.name.as_str()], Some(&desc.data_type)) + fn access_field(&self, desc: &SourceColumnDesc) -> AccessResult> { + self.1.access(&[desc.name.as_str()], &desc.data_type) } } - -#[derive(Error, Debug, Macro)] -#[thiserror_ext(macro(mangle))] -pub enum AccessError { - #[error("Undefined field `{name}` at `{path}`")] - Undefined { name: String, path: String }, - #[error("Expected type `{expected}` but got `{got}` for `{value}`")] - TypeError { - expected: String, - got: String, - value: String, - }, - #[error("Unsupported data type `{ty}`")] - UnsupportedType { ty: String }, - - /// Errors that are not categorized into variants above. - #[error("{message}")] - Uncategorized { message: String }, -} diff --git a/src/connector/src/parser/unified/protobuf.rs b/src/connector/src/parser/unified/protobuf.rs index cd9178c7dd08d..02febc22db247 100644 --- a/src/connector/src/parser/unified/protobuf.rs +++ b/src/connector/src/parser/unified/protobuf.rs @@ -12,16 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::borrow::Cow; use std::sync::{Arc, LazyLock}; use prost_reflect::{DescriptorPool, DynamicMessage, ReflectMessage}; use risingwave_common::log::LogSuppresser; -use risingwave_common::types::DataType; +use risingwave_common::types::{DataType, DatumCow, ToOwnedDatum}; use thiserror_ext::AsReport; use super::{Access, AccessResult}; use crate::parser::from_protobuf_value; -use crate::parser::unified::{uncategorized, AccessError}; +use crate::parser::unified::uncategorized; pub struct ProtobufAccess { message: DynamicMessage, @@ -38,7 +39,11 @@ impl ProtobufAccess { } impl Access for ProtobufAccess { - fn access(&self, path: &[&str], _type_expected: Option<&DataType>) -> AccessResult { + fn access<'a>( + &'a self, + path: &[&str], + _type_expected: &DataType, + ) -> AccessResult> { debug_assert_eq!(1, path.len()); let field_desc = self .message @@ -52,8 +57,14 @@ impl Access for ProtobufAccess { tracing::error!(suppressed_count, "{}", e.as_report()); } })?; - let value = self.message.get_field(&field_desc); - from_protobuf_value(&field_desc, &value, &self.descriptor_pool) + match self.message.get_field(&field_desc) { + Cow::Borrowed(value) => from_protobuf_value(&field_desc, value, &self.descriptor_pool), + + // `Owned` variant occurs only if there's no such field and the default value is returned. + Cow::Owned(value) => from_protobuf_value(&field_desc, &value, &self.descriptor_pool) + // enforce `Owned` variant to avoid returning a reference to a temporary value + .map(|d| d.to_owned_datum().into()), + } } } diff --git a/src/connector/src/parser/unified/upsert.rs b/src/connector/src/parser/unified/upsert.rs deleted file mode 100644 index 9129f0d16d864..0000000000000 --- a/src/connector/src/parser/unified/upsert.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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 risingwave_common::types::DataType; -use risingwave_pb::plan_common::additional_column::ColumnType as AdditionalColumnType; - -use super::{Access, ChangeEvent, ChangeEventOperation}; -use crate::parser::unified::AccessError; -use crate::source::SourceColumnDesc; - -/// `UpsertAccess` wraps a key-value message format into an upsert source. -/// A key accessor and a value accessor are required. -pub struct UpsertChangeEvent { - key_accessor: Option, - value_accessor: Option, - key_column_name: Option, -} - -impl Default for UpsertChangeEvent { - fn default() -> Self { - Self { - key_accessor: None, - value_accessor: None, - key_column_name: None, - } - } -} - -impl UpsertChangeEvent { - pub fn with_key(mut self, key: K) -> Self - where - K: Access, - { - self.key_accessor = Some(key); - self - } - - pub fn with_value(mut self, value: V) -> Self - where - V: Access, - { - self.value_accessor = Some(value); - self - } - - pub fn with_key_column_name(mut self, name: impl ToString) -> Self { - self.key_column_name = Some(name.to_string()); - self - } -} - -impl Access for UpsertChangeEvent -where - K: Access, - V: Access, -{ - fn access(&self, path: &[&str], type_expected: Option<&DataType>) -> super::AccessResult { - let create_error = |name: String| AccessError::Undefined { - name, - path: String::new(), - }; - match path.first() { - Some(&"key") => { - if let Some(ka) = &self.key_accessor { - ka.access(&path[1..], type_expected) - } else { - Err(create_error("key".to_string())) - } - } - Some(&"value") => { - if let Some(va) = &self.value_accessor { - va.access(&path[1..], type_expected) - } else { - Err(create_error("value".to_string())) - } - } - None => Ok(None), - Some(other) => Err(create_error(other.to_string())), - } - } -} - -impl ChangeEvent for UpsertChangeEvent -where - K: Access, - V: Access, -{ - fn op(&self) -> std::result::Result { - if let Ok(Some(_)) = self.access(&["value"], None) { - Ok(ChangeEventOperation::Upsert) - } else { - Ok(ChangeEventOperation::Delete) - } - } - - fn access_field(&self, desc: &SourceColumnDesc) -> super::AccessResult { - match desc.additional_column.column_type { - Some(AdditionalColumnType::Key(_)) => { - if let Some(key_as_column_name) = &self.key_column_name - && &desc.name == key_as_column_name - { - self.access(&["key"], Some(&desc.data_type)) - } else { - self.access(&["key", &desc.name], Some(&desc.data_type)) - } - } - None => self.access(&["value", &desc.name], Some(&desc.data_type)), - _ => unreachable!(), - } - } -} diff --git a/src/connector/src/parser/unified/util.rs b/src/connector/src/parser/unified/util.rs index 948190edf685a..2ed7e1fbefd4e 100644 --- a/src/connector/src/parser/unified/util.rs +++ b/src/connector/src/parser/unified/util.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{Access, AccessResult, ChangeEvent}; +use super::{AccessResult, ChangeEvent}; use crate::parser::unified::ChangeEventOperation; use crate::parser::SourceStreamChunkRowWriter; use crate::source::SourceColumnDesc; @@ -24,8 +24,8 @@ pub fn apply_row_operation_on_stream_chunk_writer_with_op( ) -> AccessResult<()> { let f = |column: &SourceColumnDesc| row_op.access_field(column); match op { - ChangeEventOperation::Upsert => writer.insert(f), - ChangeEventOperation::Delete => writer.delete(f), + ChangeEventOperation::Upsert => writer.do_insert(f), + ChangeEventOperation::Delete => writer.do_delete(f), } } @@ -36,10 +36,3 @@ pub fn apply_row_operation_on_stream_chunk_writer( let op = row_op.op()?; apply_row_operation_on_stream_chunk_writer_with_op(row_op, writer, op) } - -pub fn apply_row_accessor_on_stream_chunk_writer( - accessor: impl Access, - writer: &mut SourceStreamChunkRowWriter<'_>, -) -> AccessResult<()> { - writer.insert(|column| accessor.access(&[&column.name], Some(&column.data_type))) -} diff --git a/src/connector/src/parser/upsert_parser.rs b/src/connector/src/parser/upsert_parser.rs index 048fd0beca3ff..df5e3b66e3136 100644 --- a/src/connector/src/parser/upsert_parser.rs +++ b/src/connector/src/parser/upsert_parser.rs @@ -16,14 +16,13 @@ use risingwave_common::bail; use risingwave_pb::plan_common::additional_column::ColumnType as AdditionalColumnType; use super::bytes_parser::BytesAccessBuilder; -use super::unified::upsert::UpsertChangeEvent; -use super::unified::util::apply_row_operation_on_stream_chunk_writer_with_op; use super::unified::{AccessImpl, ChangeEventOperation}; use super::{ AccessBuilderImpl, ByteStreamSourceParser, BytesProperties, EncodingProperties, EncodingType, SourceStreamChunkRowWriter, SpecificParserConfig, }; use crate::error::ConnectorResult; +use crate::parser::unified::kv_event::KvEvent; use crate::parser::ParserFormat; use crate::source::{SourceColumnDesc, SourceContext, SourceContextRef}; @@ -97,22 +96,26 @@ impl UpsertParser { payload: Option>, mut writer: SourceStreamChunkRowWriter<'_>, ) -> ConnectorResult<()> { - let mut row_op: UpsertChangeEvent, AccessImpl<'_, '_>> = - UpsertChangeEvent::default(); - let mut change_event_op = ChangeEventOperation::Delete; + let mut row_op: KvEvent, AccessImpl<'_>> = KvEvent::default(); if let Some(data) = key { - row_op = row_op.with_key(self.key_builder.generate_accessor(data).await?); + row_op.with_key(self.key_builder.generate_accessor(data).await?); } // Empty payload of kafka is Some(vec![]) + let change_event_op; if let Some(data) = payload && !data.is_empty() { - row_op = row_op.with_value(self.payload_builder.generate_accessor(data).await?); + row_op.with_value(self.payload_builder.generate_accessor(data).await?); change_event_op = ChangeEventOperation::Upsert; + } else { + change_event_op = ChangeEventOperation::Delete; } - - apply_row_operation_on_stream_chunk_writer_with_op(row_op, &mut writer, change_event_op) - .map_err(Into::into) + let f = |column: &SourceColumnDesc| row_op.access_field(column); + match change_event_op { + ChangeEventOperation::Upsert => writer.do_insert(f)?, + ChangeEventOperation::Delete => writer.do_delete(f)?, + } + Ok(()) } } diff --git a/src/connector/src/parser/util.rs b/src/connector/src/parser/util.rs index 57091d701fef6..590ba927854d3 100644 --- a/src/connector/src/parser/util.rs +++ b/src/connector/src/parser/util.rs @@ -11,13 +11,13 @@ // 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::HashMap; +use std::collections::BTreeMap; use anyhow::Context; use bytes::Bytes; use reqwest::Url; use risingwave_common::bail; -use risingwave_common::types::Datum; +use risingwave_common::types::{Datum, DatumCow, DatumRef}; use risingwave_pb::data::DataType as PbDataType; use crate::aws_utils::load_file_descriptor_from_s3; @@ -25,8 +25,27 @@ use crate::connector_common::AwsAuthProps; use crate::error::ConnectorResult; use crate::source::SourceMeta; +macro_rules! log_error { + ($name:expr, $err:expr, $message:expr) => { + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::error!( + column = $name, + error = %$err.as_report(), + suppressed_count, + $message, + ); + } + }; +} +pub(crate) use log_error; +use risingwave_pb::plan_common::additional_column; +use risingwave_pb::plan_common::additional_column::ColumnType; + +use crate::parser::{AccessError, AccessResult}; +use crate::source::cdc::DebeziumCdcMeta; + /// get kafka topic name -pub(super) fn get_kafka_topic(props: &HashMap) -> ConnectorResult<&String> { +pub(super) fn get_kafka_topic(props: &BTreeMap) -> ConnectorResult<&String> { const KAFKA_TOPIC_KEY1: &str = "kafka.topic"; const KAFKA_TOPIC_KEY2: &str = "topic"; @@ -73,22 +92,6 @@ macro_rules! only_parse_payload { }; } -// Extract encoding config and encoding type from ParserProperties -// for message key. -// -// Suppose (A, B) is the combination of key/payload combination: -// For (None, B), key should be the the key setting from B -// For (A, B), key should be the value setting from A -#[macro_export] -macro_rules! extract_key_config { - ($props:ident) => { - match $props.key_encoding_config { - Some(config) => (config, EncodingType::Value), - None => ($props.encoding_config.clone(), EncodingType::Key), - } - }; -} - /// Load raw bytes from: /// * local file, for on-premise or testing. /// * http/https, for common usage. @@ -113,13 +116,29 @@ pub(super) async fn bytes_from_url( } } -pub fn extreact_timestamp_from_meta(meta: &SourceMeta) -> Option { +pub fn extreact_timestamp_from_meta(meta: &SourceMeta) -> Option> { match meta { SourceMeta::Kafka(kafka_meta) => kafka_meta.extract_timestamp(), + SourceMeta::DebeziumCdc(cdc_meta) => Some(cdc_meta.extract_timestamp()), _ => None, } } +pub fn extract_cdc_meta_column<'a>( + cdc_meta: &'a DebeziumCdcMeta, + column_type: &additional_column::ColumnType, + column_name: &str, +) -> AccessResult> { + match column_type { + ColumnType::Timestamp(_) => Ok(cdc_meta.extract_timestamp()), + ColumnType::DatabaseName(_) => Ok(cdc_meta.extract_database_name()), + ColumnType::TableName(_) => Ok(cdc_meta.extract_table_name()), + _ => Err(AccessError::UnsupportedAdditionalColumn { + name: column_name.to_string(), + }), + } +} + pub fn extract_headers_from_meta(meta: &SourceMeta) -> Option { match meta { SourceMeta::Kafka(kafka_meta) => kafka_meta.extract_headers(), /* expect output of type `array[struct]` */ @@ -127,11 +146,11 @@ pub fn extract_headers_from_meta(meta: &SourceMeta) -> Option { } } -pub fn extract_header_inner_from_meta( - meta: &SourceMeta, +pub fn extract_header_inner_from_meta<'a>( + meta: &'a SourceMeta, inner_field: &str, data_type: Option<&PbDataType>, -) -> Option { +) -> Option> { match meta { SourceMeta::Kafka(kafka_meta) => kafka_meta.extract_header_inner(inner_field, data_type), /* expect output of type `bytea` or `varchar` */ _ => None, diff --git a/src/connector/src/schema/avro.rs b/src/connector/src/schema/avro.rs index 22c5fb4acadd1..557953dc5b342 100644 --- a/src/connector/src/schema/avro.rs +++ b/src/connector/src/schema/avro.rs @@ -12,42 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; -use std::sync::Arc; - use apache_avro::Schema as AvroSchema; -use super::loader::{LoadedSchema, SchemaLoader}; +use super::loader::LoadedSchema; use super::schema_registry::Subject; use super::SchemaFetchError; -pub struct SchemaWithId { - pub schema: Arc, - pub id: i32, -} - -/// Schema registry only -pub async fn fetch_schema( - format_options: &BTreeMap, - topic: &str, -) -> Result<(SchemaWithId, SchemaWithId), SchemaFetchError> { - let loader = SchemaLoader::from_format_options(topic, format_options)?; - - let (key_id, key_avro) = loader.load_key_schema().await?; - let (val_id, val_avro) = loader.load_val_schema().await?; - - Ok(( - SchemaWithId { - id: key_id, - schema: Arc::new(key_avro), - }, - SchemaWithId { - id: val_id, - schema: Arc::new(val_avro), - }, - )) -} - impl LoadedSchema for AvroSchema { fn compile(primary: Subject, _: Vec) -> Result { AvroSchema::parse_str(&primary.schema.content) diff --git a/src/connector/src/schema/loader.rs b/src/connector/src/schema/loader.rs index a50d8cced575b..0deb5c1e4d0ce 100644 --- a/src/connector/src/schema/loader.rs +++ b/src/connector/src/schema/loader.rs @@ -19,13 +19,10 @@ use risingwave_pb::catalog::PbSchemaRegistryNameStrategy; use super::schema_registry::{ get_subject_by_strategy, handle_sr_list, name_strategy_from_str, Client, Subject, }; -use super::{invalid_option_error, InvalidOptionError, SchemaFetchError}; - -const MESSAGE_NAME_KEY: &str = "message"; -const KEY_MESSAGE_NAME_KEY: &str = "key.message"; -const SCHEMA_LOCATION_KEY: &str = "schema.location"; -const SCHEMA_REGISTRY_KEY: &str = "schema.registry"; -const NAME_STRATEGY_KEY: &str = "schema.registry.name.strategy"; +use super::{ + invalid_option_error, InvalidOptionError, SchemaFetchError, KEY_MESSAGE_NAME_KEY, + MESSAGE_NAME_KEY, NAME_STRATEGY_KEY, SCHEMA_REGISTRY_KEY, +}; pub struct SchemaLoader { pub client: Client, diff --git a/src/connector/src/schema/schema_registry/util.rs b/src/connector/src/schema/schema_registry/util.rs index 0d43f33baa31c..a02d1deaf390d 100644 --- a/src/connector/src/schema/schema_registry/util.rs +++ b/src/connector/src/schema/schema_registry/util.rs @@ -53,11 +53,15 @@ pub enum WireFormatError { ParseMessageIndexes, } -/// extract the magic number and `schema_id` at the front of payload +/// Returns `(schema_id, payload)` /// -/// 0 -> magic number -/// 1-4 -> schema id -/// 5-... -> message payload +/// Refer to [Confluent schema registry wire format](https://docs.confluent.io/platform/7.6/schema-registry/fundamentals/serdes-develop/index.html#wire-format) +/// +/// | Bytes | Area | Description | +/// |-------|-------------|----------------------------------------------------------------------------------------------------| +/// | 0 | Magic Byte | Confluent serialization format version number; currently always `0`. | +/// | 1-4 | Schema ID | 4-byte schema ID as returned by Schema Registry. | +/// | 5-... | Data | Serialized data for the specified schema format (for example, binary encoding for Avro or Protobuf.| pub(crate) fn extract_schema_id(payload: &[u8]) -> Result<(i32, &[u8]), WireFormatError> { use byteorder::{BigEndian, ReadBytesExt as _}; @@ -150,6 +154,7 @@ pub struct Subject { #[derive(Debug, Deserialize)] pub struct SchemaReference { /// The name of the reference. + #[allow(dead_code)] pub name: String, /// The subject that the referenced schema belongs to pub subject: String, diff --git a/src/connector/src/sink/big_query.rs b/src/connector/src/sink/big_query.rs index e7f74614c9cf0..04f3360b1a02a 100644 --- a/src/connector/src/sink/big_query.rs +++ b/src/connector/src/sink/big_query.rs @@ -12,28 +12,48 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::mem; -use std::collections::HashMap; +use core::time::Duration; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use anyhow::anyhow; use async_trait::async_trait; +use gcp_bigquery_client::error::BQError; use gcp_bigquery_client::model::query_request::QueryRequest; -use gcp_bigquery_client::model::table_data_insert_all_request::TableDataInsertAllRequest; -use gcp_bigquery_client::model::table_data_insert_all_request_rows::TableDataInsertAllRequestRows; +use gcp_bigquery_client::model::table::Table; +use gcp_bigquery_client::model::table_field_schema::TableFieldSchema; +use gcp_bigquery_client::model::table_schema::TableSchema; use gcp_bigquery_client::Client; +use google_cloud_bigquery::grpc::apiv1::bigquery_client::StreamingWriteClient; +use google_cloud_bigquery::grpc::apiv1::conn_pool::{WriteConnectionManager, DOMAIN}; +use google_cloud_gax::conn::{ConnectionOptions, Environment}; +use google_cloud_gax::grpc::Request; +use google_cloud_googleapis::cloud::bigquery::storage::v1::append_rows_request::{ + ProtoData, Rows as AppendRowsRequestRows, +}; +use google_cloud_googleapis::cloud::bigquery::storage::v1::{ + AppendRowsRequest, ProtoRows, ProtoSchema, +}; +use google_cloud_pubsub::client::google_cloud_auth; +use google_cloud_pubsub::client::google_cloud_auth::credentials::CredentialsFile; +use prost_reflect::{FieldDescriptor, MessageDescriptor}; +use prost_types::{ + field_descriptor_proto, DescriptorProto, FieldDescriptorProto, FileDescriptorProto, + FileDescriptorSet, +}; use risingwave_common::array::{Op, StreamChunk}; -use risingwave_common::buffer::Bitmap; -use risingwave_common::catalog::Schema; +use risingwave_common::bitmap::Bitmap; +use risingwave_common::catalog::{Field, Schema}; use risingwave_common::types::DataType; use serde_derive::Deserialize; -use serde_json::Value; use serde_with::{serde_as, DisplayFromStr}; +use simd_json::prelude::ArrayTrait; use url::Url; +use uuid::Uuid; use with_options::WithOptions; use yup_oauth2::ServiceAccountKey; -use super::encoder::{JsonEncoder, RowEncoder}; +use super::encoder::{ProtoEncoder, ProtoHeader, RowEncoder, SerTo}; use super::writer::LogSinkerOf; use super::{SinkError, SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, SINK_TYPE_UPSERT}; use crate::aws_utils::load_file_descriptor_from_s3; @@ -44,6 +64,10 @@ use crate::sink::{ }; pub const BIGQUERY_SINK: &str = "bigquery"; +pub const CHANGE_TYPE: &str = "_CHANGE_TYPE"; +const DEFAULT_GRPC_CHANNEL_NUMS: usize = 4; +const CONNECT_TIMEOUT: Option = Some(Duration::from_secs(30)); +const CONNECTION_TIMEOUT: Option = None; #[serde_as] #[derive(Deserialize, Debug, Clone, WithOptions)] @@ -61,34 +85,61 @@ pub struct BigQueryCommon { #[serde(rename = "bigquery.max_batch_rows", default = "default_max_batch_rows")] #[serde_as(as = "DisplayFromStr")] pub max_batch_rows: usize, + #[serde(rename = "bigquery.retry_times", default = "default_retry_times")] + #[serde_as(as = "DisplayFromStr")] + pub retry_times: usize, + #[serde(default)] // default false + #[serde_as(as = "DisplayFromStr")] + pub auto_create: bool, } fn default_max_batch_rows() -> usize { 1024 } +fn default_retry_times() -> usize { + 5 +} + impl BigQueryCommon { - pub(crate) async fn build_client(&self, aws_auth_props: &AwsAuthProps) -> Result { - let service_account = if let Some(local_path) = &self.local_path { - let auth_json = std::fs::read_to_string(local_path) - .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))?; - serde_json::from_str::(&auth_json) - .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))? + async fn build_client(&self, aws_auth_props: &AwsAuthProps) -> Result { + let auth_json = self.get_auth_json_from_path(aws_auth_props).await?; + + let service_account = serde_json::from_str::(&auth_json) + .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))?; + let client: Client = Client::from_service_account_key(service_account, false) + .await + .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))?; + Ok(client) + } + + async fn build_writer_client( + &self, + aws_auth_props: &AwsAuthProps, + ) -> Result { + let auth_json = self.get_auth_json_from_path(aws_auth_props).await?; + + let credentials_file = CredentialsFile::new_from_str(&auth_json) + .await + .map_err(|e| SinkError::BigQuery(e.into()))?; + let client = StorageWriterClient::new(credentials_file).await?; + Ok(client) + } + + async fn get_auth_json_from_path(&self, aws_auth_props: &AwsAuthProps) -> Result { + if let Some(local_path) = &self.local_path { + std::fs::read_to_string(local_path) + .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err))) } else if let Some(s3_path) = &self.s3_path { let url = Url::parse(s3_path).map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))?; - let auth_json = load_file_descriptor_from_s3(&url, aws_auth_props) + let auth_vec = load_file_descriptor_from_s3(&url, aws_auth_props) .await .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))?; - serde_json::from_slice::(&auth_json) - .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))? + Ok(String::from_utf8(auth_vec).map_err(|e| SinkError::BigQuery(e.into()))?) } else { - return Err(SinkError::BigQuery(anyhow::anyhow!("`bigquery.local.path` and `bigquery.s3.path` set at least one, configure as needed."))); - }; - let client: Client = Client::from_service_account_key(service_account, false) - .await - .map_err(|err| SinkError::BigQuery(anyhow::anyhow!(err)))?; - Ok(client) + Err(SinkError::BigQuery(anyhow::anyhow!("`bigquery.local.path` and `bigquery.s3.path` set at least one, configure as needed."))) + } } } @@ -102,7 +153,7 @@ pub struct BigQueryConfig { pub r#type: String, // accept "append-only" or "upsert" } impl BigQueryConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(properties).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -187,9 +238,7 @@ impl BigQuerySink { DataType::Decimal => Ok("NUMERIC".to_owned()), DataType::Date => Ok("DATE".to_owned()), DataType::Varchar => Ok("STRING".to_owned()), - DataType::Time => Err(SinkError::BigQuery(anyhow::anyhow!( - "Bigquery cannot support Time" - ))), + DataType::Time => Ok("TIME".to_owned()), DataType::Timestamp => Ok("DATETIME".to_owned()), DataType::Timestamptz => Ok("TIMESTAMP".to_owned()), DataType::Interval => Ok("INTERVAL".to_owned()), @@ -214,6 +263,79 @@ impl BigQuerySink { ))), } } + + fn map_field(rw_field: &Field) -> Result { + let tfs = match &rw_field.data_type { + DataType::Boolean => TableFieldSchema::bool(&rw_field.name), + DataType::Int16 | DataType::Int32 | DataType::Int64 | DataType::Serial => { + TableFieldSchema::integer(&rw_field.name) + } + DataType::Float32 => { + return Err(SinkError::BigQuery(anyhow::anyhow!( + "Bigquery cannot support real" + ))) + } + DataType::Float64 => TableFieldSchema::float(&rw_field.name), + DataType::Decimal => TableFieldSchema::numeric(&rw_field.name), + DataType::Date => TableFieldSchema::date(&rw_field.name), + DataType::Varchar => TableFieldSchema::string(&rw_field.name), + DataType::Time => TableFieldSchema::time(&rw_field.name), + DataType::Timestamp => TableFieldSchema::date_time(&rw_field.name), + DataType::Timestamptz => TableFieldSchema::timestamp(&rw_field.name), + DataType::Interval => { + return Err(SinkError::BigQuery(anyhow::anyhow!( + "Bigquery cannot support Interval" + ))) + } + DataType::Struct(_) => { + let mut sub_fields = Vec::with_capacity(rw_field.sub_fields.len()); + for rw_field in &rw_field.sub_fields { + let field = Self::map_field(rw_field)?; + sub_fields.push(field) + } + TableFieldSchema::record(&rw_field.name, sub_fields) + } + DataType::List(dt) => { + let inner_field = Self::map_field(&Field::with_name(*dt.clone(), &rw_field.name))?; + TableFieldSchema { + mode: Some("REPEATED".to_string()), + ..inner_field + } + } + + DataType::Bytea => TableFieldSchema::bytes(&rw_field.name), + DataType::Jsonb => TableFieldSchema::json(&rw_field.name), + DataType::Int256 => { + return Err(SinkError::BigQuery(anyhow::anyhow!( + "Bigquery cannot support Int256" + ))) + } + }; + Ok(tfs) + } + + async fn create_table( + &self, + client: &Client, + project_id: &str, + dataset_id: &str, + table_id: &str, + fields: &Vec, + ) -> Result { + let dataset = client + .dataset() + .get(project_id, dataset_id) + .await + .map_err(|e| SinkError::BigQuery(e.into()))?; + let fields: Vec<_> = fields.iter().map(Self::map_field).collect::>()?; + let table = Table::from_dataset(&dataset, table_id, TableSchema::new(fields)); + + client + .table() + .create(table) + .await + .map_err(|e| SinkError::BigQuery(e.into())) + } } impl Sink for BigQuerySink { @@ -234,27 +356,56 @@ impl Sink for BigQuerySink { } async fn validate(&self) -> Result<()> { - if !self.is_append_only { + if !self.is_append_only && self.pk_indices.is_empty() { return Err(SinkError::Config(anyhow!( - "BigQuery sink don't support upsert" - ))); + "Primary key not defined for upsert bigquery sink (please define in `primary_key` field)"))); } - let client = self .config .common .build_client(&self.config.aws_auth_props) .await?; + let BigQueryCommon { + project: project_id, + dataset: dataset_id, + table: table_id, + .. + } = &self.config.common; + + if self.config.common.auto_create { + match client + .table() + .get(project_id, dataset_id, table_id, None) + .await + { + Err(BQError::RequestError(_)) => { + // early return: no need to query schema to check column and type + return self + .create_table( + &client, + project_id, + dataset_id, + table_id, + &self.schema.fields, + ) + .await + .map(|_| ()); + } + Err(e) => return Err(SinkError::BigQuery(e.into())), + _ => {} + } + } + let mut rs = client - .job() - .query( - &self.config.common.project, - QueryRequest::new(format!( - "SELECT column_name, data_type FROM `{}.{}.INFORMATION_SCHEMA.COLUMNS` WHERE table_name = '{}'" - ,self.config.common.project,self.config.common.dataset,self.config.common.table, - )), - ) - .await.map_err(|e| SinkError::BigQuery(e.into()))?; + .job() + .query( + &self.config.common.project, + QueryRequest::new(format!( + "SELECT column_name, data_type FROM `{}.{}.INFORMATION_SCHEMA.COLUMNS` WHERE table_name = '{}'", + project_id, dataset_id, table_id, + )), + ).await.map_err(|e| SinkError::BigQuery(e.into()))?; + let mut big_query_schema = HashMap::default(); while rs.next_row() { big_query_schema.insert( @@ -278,12 +429,20 @@ impl Sink for BigQuerySink { pub struct BigQuerySinkWriter { pub config: BigQueryConfig, + #[expect(dead_code)] schema: Schema, + #[expect(dead_code)] pk_indices: Vec, - client: Client, + client: StorageWriterClient, is_append_only: bool, - insert_request: TableDataInsertAllRequest, - row_encoder: JsonEncoder, + row_encoder: ProtoEncoder, + writer_pb_schema: ProtoSchema, + #[expect(dead_code)] + message_descriptor: MessageDescriptor, + write_stream: String, + proto_field: Option, + write_rows: Vec, + write_rows_count: usize, } impl TryFrom for BigQuerySink { @@ -291,7 +450,7 @@ impl TryFrom for BigQuerySink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = BigQueryConfig::from_hashmap(param.properties)?; + let config = BigQueryConfig::from_btreemap(param.properties)?; BigQuerySink::new( config, schema, @@ -308,80 +467,169 @@ impl BigQuerySinkWriter { pk_indices: Vec, is_append_only: bool, ) -> Result { - let client = config.common.build_client(&config.aws_auth_props).await?; + let client = config + .common + .build_writer_client(&config.aws_auth_props) + .await?; + let mut descriptor_proto = build_protobuf_schema( + schema + .fields() + .iter() + .map(|f| (f.name.as_str(), &f.data_type)), + config.common.table.clone(), + )?; + + if !is_append_only { + let field = FieldDescriptorProto { + name: Some(CHANGE_TYPE.to_string()), + number: Some((schema.len() + 1) as i32), + r#type: Some(field_descriptor_proto::Type::String.into()), + ..Default::default() + }; + descriptor_proto.field.push(field); + } + + let descriptor_pool = build_protobuf_descriptor_pool(&descriptor_proto); + let message_descriptor = descriptor_pool + .get_message_by_name(&config.common.table) + .ok_or_else(|| { + SinkError::BigQuery(anyhow::anyhow!( + "Can't find message proto {}", + &config.common.table + )) + })?; + let proto_field = if !is_append_only { + let proto_field = message_descriptor + .get_field_by_name(CHANGE_TYPE) + .ok_or_else(|| { + SinkError::BigQuery(anyhow::anyhow!("Can't find {}", CHANGE_TYPE)) + })?; + Some(proto_field) + } else { + None + }; + let row_encoder = ProtoEncoder::new( + schema.clone(), + None, + message_descriptor.clone(), + ProtoHeader::None, + )?; Ok(Self { + write_stream: format!( + "projects/{}/datasets/{}/tables/{}/streams/_default", + config.common.project, config.common.dataset, config.common.table + ), config, - schema: schema.clone(), + schema, pk_indices, client, is_append_only, - insert_request: TableDataInsertAllRequest::new(), - row_encoder: JsonEncoder::new_with_bigquery(schema, None), + row_encoder, + message_descriptor, + proto_field, + writer_pb_schema: ProtoSchema { + proto_descriptor: Some(descriptor_proto), + }, + write_rows: vec![], + write_rows_count: 0, }) } - async fn append_only(&mut self, chunk: StreamChunk) -> Result<()> { - let mut insert_vec = Vec::with_capacity(chunk.capacity()); + fn append_only(&mut self, chunk: StreamChunk) -> Result>> { + let mut serialized_rows: Vec> = Vec::with_capacity(chunk.capacity()); for (op, row) in chunk.rows() { if op != Op::Insert { - return Err(SinkError::BigQuery(anyhow::anyhow!( - "BigQuery sink don't support upsert" - ))); + continue; } - insert_vec.push(TableDataInsertAllRequestRows { - insert_id: None, - json: Value::Object(self.row_encoder.encode(row)?), - }) + serialized_rows.push(self.row_encoder.encode(row)?.ser_to()?) } - self.insert_request - .add_rows(insert_vec) - .map_err(|e| SinkError::BigQuery(e.into()))?; - if self - .insert_request - .len() - .ge(&self.config.common.max_batch_rows) - { - self.insert_data().await?; + Ok(serialized_rows) + } + + fn upsert(&mut self, chunk: StreamChunk) -> Result>> { + let mut serialized_rows: Vec> = Vec::with_capacity(chunk.capacity()); + for (op, row) in chunk.rows() { + if op == Op::UpdateDelete { + continue; + } + let mut pb_row = self.row_encoder.encode(row)?; + match op { + Op::Insert => pb_row + .message + .try_set_field( + self.proto_field.as_ref().unwrap(), + prost_reflect::Value::String("UPSERT".to_string()), + ) + .map_err(|e| SinkError::BigQuery(e.into()))?, + Op::Delete => pb_row + .message + .try_set_field( + self.proto_field.as_ref().unwrap(), + prost_reflect::Value::String("DELETE".to_string()), + ) + .map_err(|e| SinkError::BigQuery(e.into()))?, + Op::UpdateDelete => continue, + Op::UpdateInsert => pb_row + .message + .try_set_field( + self.proto_field.as_ref().unwrap(), + prost_reflect::Value::String("UPSERT".to_string()), + ) + .map_err(|e| SinkError::BigQuery(e.into()))?, + }; + + serialized_rows.push(pb_row.ser_to()?) } - Ok(()) + Ok(serialized_rows) } - async fn insert_data(&mut self) -> Result<()> { - if !self.insert_request.is_empty() { - let insert_request = - mem::replace(&mut self.insert_request, TableDataInsertAllRequest::new()); - let request = self + async fn write_rows(&mut self) -> Result<()> { + if self.write_rows.is_empty() { + return Ok(()); + } + let mut errs = Vec::with_capacity(self.config.common.retry_times); + for _ in 0..self.config.common.retry_times { + match self .client - .tabledata() - .insert_all( - &self.config.common.project, - &self.config.common.dataset, - &self.config.common.table, - insert_request, - ) + .append_rows(self.write_rows.clone(), self.write_stream.clone()) .await - .map_err(|e| SinkError::BigQuery(e.into()))?; - if let Some(error) = request.insert_errors { - return Err(SinkError::BigQuery(anyhow::anyhow!( - "Insert error: {:?}", - error - ))); + { + Ok(_) => { + self.write_rows_count = 0; + self.write_rows.clear(); + return Ok(()); + } + Err(e) => errs.push(e), } } - Ok(()) + Err(SinkError::BigQuery(anyhow::anyhow!( + "Insert error {:?}", + errs + ))) } } #[async_trait] impl SinkWriter for BigQuerySinkWriter { async fn write_batch(&mut self, chunk: StreamChunk) -> Result<()> { - if self.is_append_only { - self.append_only(chunk).await + let serialized_rows = if self.is_append_only { + self.append_only(chunk)? } else { - Err(SinkError::BigQuery(anyhow::anyhow!( - "BigQuery sink don't support upsert" - ))) + self.upsert(chunk)? + }; + if !serialized_rows.is_empty() { + self.write_rows_count += serialized_rows.len(); + let rows = AppendRowsRequestRows::ProtoRows(ProtoData { + writer_schema: Some(self.writer_pb_schema.clone()), + rows: Some(ProtoRows { serialized_rows }), + }); + self.write_rows.push(rows); + + if self.write_rows_count >= self.config.common.max_batch_rows { + self.write_rows().await?; + } } + Ok(()) } async fn begin_epoch(&mut self, _epoch: u64) -> Result<()> { @@ -392,8 +640,11 @@ impl SinkWriter for BigQuerySinkWriter { Ok(()) } - async fn barrier(&mut self, _is_checkpoint: bool) -> Result<()> { - self.insert_data().await + async fn barrier(&mut self, is_checkpoint: bool) -> Result<()> { + if is_checkpoint { + self.write_rows().await?; + } + Ok(()) } async fn update_vnode_bitmap(&mut self, _vnode_bitmap: Arc) -> Result<()> { @@ -401,11 +652,184 @@ impl SinkWriter for BigQuerySinkWriter { } } +struct StorageWriterClient { + client: StreamingWriteClient, + #[expect(dead_code)] + environment: Environment, +} +impl StorageWriterClient { + pub async fn new(credentials: CredentialsFile) -> Result { + let ts_grpc = google_cloud_auth::token::DefaultTokenSourceProvider::new_with_credentials( + Self::bigquery_grpc_auth_config(), + Box::new(credentials), + ) + .await + .map_err(|e| SinkError::BigQuery(e.into()))?; + let conn_options = ConnectionOptions { + connect_timeout: CONNECT_TIMEOUT, + timeout: CONNECTION_TIMEOUT, + }; + let environment = Environment::GoogleCloud(Box::new(ts_grpc)); + let conn = WriteConnectionManager::new( + DEFAULT_GRPC_CHANNEL_NUMS, + &environment, + DOMAIN, + &conn_options, + ) + .await + .map_err(|e| SinkError::BigQuery(e.into()))?; + let client = conn.conn(); + Ok(StorageWriterClient { + client, + environment, + }) + } + + pub async fn append_rows( + &mut self, + rows: Vec, + write_stream: String, + ) -> Result<()> { + let mut resp_count = rows.len(); + let append_req: Vec = rows + .into_iter() + .map(|row| AppendRowsRequest { + write_stream: write_stream.clone(), + offset: None, + trace_id: Uuid::new_v4().hyphenated().to_string(), + missing_value_interpretations: HashMap::default(), + rows: Some(row), + }) + .collect(); + let mut resp = self + .client + .append_rows(Request::new(tokio_stream::iter(append_req))) + .await + .map_err(|e| SinkError::BigQuery(e.into()))? + .into_inner(); + while let Some(append_rows_response) = resp + .message() + .await + .map_err(|e| SinkError::BigQuery(e.into()))? + { + resp_count -= 1; + if !append_rows_response.row_errors.is_empty() { + return Err(SinkError::BigQuery(anyhow::anyhow!( + "Insert error {:?}", + append_rows_response.row_errors + ))); + } + } + assert_eq!(resp_count,0,"bigquery sink insert error: the number of response inserted is not equal to the number of request"); + Ok(()) + } + + fn bigquery_grpc_auth_config() -> google_cloud_auth::project::Config<'static> { + google_cloud_auth::project::Config { + audience: Some(google_cloud_bigquery::grpc::apiv1::conn_pool::AUDIENCE), + scopes: Some(&google_cloud_bigquery::grpc::apiv1::conn_pool::SCOPES), + sub: None, + } + } +} + +fn build_protobuf_descriptor_pool(desc: &DescriptorProto) -> prost_reflect::DescriptorPool { + let file_descriptor = FileDescriptorProto { + message_type: vec![desc.clone()], + name: Some("bigquery".to_string()), + ..Default::default() + }; + + prost_reflect::DescriptorPool::from_file_descriptor_set(FileDescriptorSet { + file: vec![file_descriptor], + }) + .unwrap() +} + +fn build_protobuf_schema<'a>( + fields: impl Iterator, + name: String, +) -> Result { + let mut proto = DescriptorProto { + name: Some(name), + ..Default::default() + }; + let mut struct_vec = vec![]; + let field_vec = fields + .enumerate() + .map(|(index, (name, data_type))| { + let (field, des_proto) = + build_protobuf_field(data_type, (index + 1) as i32, name.to_string())?; + if let Some(sv) = des_proto { + struct_vec.push(sv); + } + Ok(field) + }) + .collect::>>()?; + proto.field = field_vec; + proto.nested_type = struct_vec; + Ok(proto) +} + +fn build_protobuf_field( + data_type: &DataType, + index: i32, + name: String, +) -> Result<(FieldDescriptorProto, Option)> { + let mut field = FieldDescriptorProto { + name: Some(name.clone()), + number: Some(index), + ..Default::default() + }; + match data_type { + DataType::Boolean => field.r#type = Some(field_descriptor_proto::Type::Bool.into()), + DataType::Int32 => field.r#type = Some(field_descriptor_proto::Type::Int32.into()), + DataType::Int16 | DataType::Int64 => { + field.r#type = Some(field_descriptor_proto::Type::Int64.into()) + } + DataType::Float64 => field.r#type = Some(field_descriptor_proto::Type::Double.into()), + DataType::Decimal => field.r#type = Some(field_descriptor_proto::Type::String.into()), + DataType::Date => field.r#type = Some(field_descriptor_proto::Type::Int32.into()), + DataType::Varchar => field.r#type = Some(field_descriptor_proto::Type::String.into()), + DataType::Time => field.r#type = Some(field_descriptor_proto::Type::String.into()), + DataType::Timestamp => field.r#type = Some(field_descriptor_proto::Type::String.into()), + DataType::Timestamptz => field.r#type = Some(field_descriptor_proto::Type::String.into()), + DataType::Interval => field.r#type = Some(field_descriptor_proto::Type::String.into()), + DataType::Struct(s) => { + field.r#type = Some(field_descriptor_proto::Type::Message.into()); + let name = format!("Struct{}", name); + let sub_proto = build_protobuf_schema(s.iter(), name.clone())?; + field.type_name = Some(name); + return Ok((field, Some(sub_proto))); + } + DataType::List(l) => { + let (mut field, proto) = build_protobuf_field(l.as_ref(), index, name.clone())?; + field.label = Some(field_descriptor_proto::Label::Repeated.into()); + return Ok((field, proto)); + } + DataType::Bytea => field.r#type = Some(field_descriptor_proto::Type::Bytes.into()), + DataType::Jsonb => field.r#type = Some(field_descriptor_proto::Type::String.into()), + DataType::Serial => field.r#type = Some(field_descriptor_proto::Type::Int64.into()), + DataType::Float32 | DataType::Int256 => { + return Err(SinkError::BigQuery(anyhow::anyhow!( + "Don't support Float32 and Int256" + ))) + } + } + Ok((field, None)) +} + #[cfg(test)] mod test { + + use std::assert_matches::assert_matches; + + use risingwave_common::catalog::{Field, Schema}; use risingwave_common::types::{DataType, StructType}; - use crate::sink::big_query::BigQuerySink; + use crate::sink::big_query::{ + build_protobuf_descriptor_pool, build_protobuf_schema, BigQuerySink, + }; #[tokio::test] async fn test_type_check() { @@ -425,4 +849,63 @@ mod test { big_query_type_string ); } + + #[tokio::test] + async fn test_schema_check() { + let schema = Schema { + fields: vec![ + Field::with_name(DataType::Int64, "v1"), + Field::with_name(DataType::Float64, "v2"), + Field::with_name( + DataType::List(Box::new(DataType::Struct(StructType::new(vec![ + ("v1".to_owned(), DataType::List(Box::new(DataType::Int64))), + ( + "v3".to_owned(), + DataType::Struct(StructType::new(vec![ + ("v1".to_owned(), DataType::Int64), + ("v2".to_owned(), DataType::Int64), + ])), + ), + ])))), + "v3", + ), + ], + }; + let fields = schema + .fields() + .iter() + .map(|f| (f.name.as_str(), &f.data_type)); + let desc = build_protobuf_schema(fields, "t1".to_string()).unwrap(); + let pool = build_protobuf_descriptor_pool(&desc); + let t1_message = pool.get_message_by_name("t1").unwrap(); + assert_matches!( + t1_message.get_field_by_name("v1").unwrap().kind(), + prost_reflect::Kind::Int64 + ); + assert_matches!( + t1_message.get_field_by_name("v2").unwrap().kind(), + prost_reflect::Kind::Double + ); + assert_matches!( + t1_message.get_field_by_name("v3").unwrap().kind(), + prost_reflect::Kind::Message(_) + ); + + let v3_message = pool.get_message_by_name("t1.Structv3").unwrap(); + assert_matches!( + v3_message.get_field_by_name("v1").unwrap().kind(), + prost_reflect::Kind::Int64 + ); + assert!(v3_message.get_field_by_name("v1").unwrap().is_list()); + + let v3_v3_message = pool.get_message_by_name("t1.Structv3.Structv3").unwrap(); + assert_matches!( + v3_v3_message.get_field_by_name("v1").unwrap().kind(), + prost_reflect::Kind::Int64 + ); + assert_matches!( + v3_v3_message.get_field_by_name("v2").unwrap().kind(), + prost_reflect::Kind::Int64 + ); + } } diff --git a/src/connector/src/sink/boxed.rs b/src/connector/src/sink/boxed.rs index bb3edaa22f087..f31e3b0a06805 100644 --- a/src/connector/src/sink/boxed.rs +++ b/src/connector/src/sink/boxed.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use async_trait::async_trait; use risingwave_common::array::StreamChunk; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_pb::connector_service::SinkMetadata; use crate::sink::{SinkCommitCoordinator, SinkWriter}; diff --git a/src/connector/src/sink/catalog/desc.rs b/src/connector/src/sink/catalog/desc.rs index 0fb466e5a1742..64e618bdd933e 100644 --- a/src/connector/src/sink/catalog/desc.rs +++ b/src/connector/src/sink/catalog/desc.rs @@ -19,6 +19,7 @@ use risingwave_common::catalog::{ ColumnCatalog, ConnectionId, CreateType, DatabaseId, SchemaId, TableId, UserId, }; use risingwave_common::util::sort_util::ColumnOrder; +use risingwave_pb::secret::PbSecretRef; use risingwave_pb::stream_plan::PbSinkDesc; use super::{SinkCatalog, SinkFormatDesc, SinkId, SinkType}; @@ -83,6 +84,7 @@ impl SinkDesc { owner: UserId, connection_id: Option, dependent_relations: Vec, + secret_ref: BTreeMap, ) -> SinkCatalog { SinkCatalog { id: self.id, @@ -96,7 +98,8 @@ impl SinkDesc { distribution_key: self.distribution_key, owner, dependent_relations, - properties: self.properties.into_iter().collect(), + properties: self.properties, + secret_refs: secret_ref, sink_type: self.sink_type, format_desc: self.format_desc, connection_id, @@ -131,6 +134,7 @@ impl SinkDesc { sink_from_name: self.sink_from_name.clone(), target_table: self.target_table.map(|table_id| table_id.table_id()), extra_partition_col_idx: self.extra_partition_col_idx.map(|idx| idx as u64), + secret_refs: Default::default(), } } } diff --git a/src/connector/src/sink/catalog/mod.rs b/src/connector/src/sink/catalog/mod.rs index ce4ceb250d515..7638c197f2342 100644 --- a/src/connector/src/sink/catalog/mod.rs +++ b/src/connector/src/sink/catalog/mod.rs @@ -14,7 +14,7 @@ pub mod desc; -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use anyhow::anyhow; use itertools::Itertools; @@ -27,6 +27,7 @@ use risingwave_common::util::sort_util::ColumnOrder; use risingwave_pb::catalog::{ PbCreateType, PbSink, PbSinkFormatDesc, PbSinkType, PbStreamJobStatus, }; +use risingwave_pb::secret::PbSecretRef; use super::{ SinkError, CONNECTOR_TYPE_KEY, SINK_TYPE_APPEND_ONLY, SINK_TYPE_DEBEZIUM, SINK_TYPE_OPTION, @@ -119,6 +120,8 @@ pub struct SinkFormatDesc { pub format: SinkFormat, pub encode: SinkEncode, pub options: BTreeMap, + + pub key_encode: Option, } /// TODO: consolidate with [`crate::source::SourceFormat`] and [`crate::parser::ProtocolProperties`]. @@ -136,6 +139,7 @@ pub enum SinkEncode { Protobuf, Avro, Template, + Text, } impl SinkFormatDesc { @@ -166,6 +170,7 @@ impl SinkFormatDesc { format, encode, options: Default::default(), + key_encode: None, })) } @@ -177,12 +182,16 @@ impl SinkFormatDesc { SinkFormat::Upsert => F::Upsert, SinkFormat::Debezium => F::Debezium, }; - let encode = match self.encode { + let mapping_encode = |sink_encode: &SinkEncode| match sink_encode { SinkEncode::Json => E::Json, SinkEncode::Protobuf => E::Protobuf, SinkEncode::Avro => E::Avro, SinkEncode::Template => E::Template, + SinkEncode::Text => E::Text, }; + + let encode = mapping_encode(&self.encode); + let key_encode = self.key_encode.as_ref().map(|e| mapping_encode(e).into()); let options = self .options .iter() @@ -193,6 +202,8 @@ impl SinkFormatDesc { format: format.into(), encode: encode.into(), options, + key_encode, + secret_refs: Default::default(), } } } @@ -224,19 +235,44 @@ impl TryFrom for SinkFormatDesc { E::Protobuf => SinkEncode::Protobuf, E::Template => SinkEncode::Template, E::Avro => SinkEncode::Avro, - e @ (E::Unspecified | E::Native | E::Csv | E::Bytes | E::None | E::Parquet) => { + e @ (E::Unspecified + | E::Native + | E::Csv + | E::Bytes + | E::None + | E::Text + | E::Parquet) => { return Err(SinkError::Config(anyhow!( "sink encode unsupported: {}", e.as_str_name() ))) } }; + let key_encode = match &value.key_encode() { + E::Text => Some(SinkEncode::Text), + E::Unspecified => None, + encode @ (E::Avro + | E::Bytes + | E::Csv + | E::Json + | E::Protobuf + | E::Template + | E::Native + | E::Parquet + | E::None) => { + return Err(SinkError::Config(anyhow!( + "unsupported {} as sink key encode", + encode.as_str_name() + ))) + } + }; let options = value.options.into_iter().collect(); Ok(Self { format, encode, options, + key_encode, }) } } @@ -276,7 +312,7 @@ pub struct SinkCatalog { pub distribution_key: Vec, /// The properties of the sink. - pub properties: HashMap, + pub properties: BTreeMap, /// Owner of the sink. pub owner: UserId, @@ -310,6 +346,9 @@ pub struct SinkCatalog { pub created_at_cluster_version: Option, pub initialized_at_cluster_version: Option, pub create_type: CreateType, + + /// The secret reference for the sink, mapping from property name to secret id. + pub secret_refs: BTreeMap, } impl SinkCatalog { @@ -351,6 +390,7 @@ impl SinkCatalog { created_at_cluster_version: self.created_at_cluster_version.clone(), initialized_at_cluster_version: self.initialized_at_cluster_version.clone(), create_type: self.create_type.to_proto() as i32, + secret_refs: self.secret_refs.clone(), } } @@ -444,6 +484,7 @@ impl From for SinkCatalog { initialized_at_cluster_version: pb.initialized_at_cluster_version, created_at_cluster_version: pb.created_at_cluster_version, create_type: CreateType::from_proto(create_type), + secret_refs: pb.secret_refs, } } } diff --git a/src/connector/src/sink/clickhouse.rs b/src/connector/src/sink/clickhouse.rs index 5329118c73d61..bbeb27aa4514c 100644 --- a/src/connector/src/sink/clickhouse.rs +++ b/src/connector/src/sink/clickhouse.rs @@ -13,13 +13,16 @@ // limitations under the License. use core::fmt::Debug; -use std::collections::{HashMap, HashSet}; +use core::num::NonZeroU64; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::sync::Arc; use anyhow::anyhow; use clickhouse::insert::Insert; use clickhouse::{Client as ClickHouseClient, Row as ClickHouseRow}; use itertools::Itertools; use risingwave_common::array::{Op, StreamChunk}; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::Schema; use risingwave_common::row::Row; use risingwave_common::session_config::sink_decouple::SinkDecouple; @@ -29,16 +32,16 @@ use serde::Serialize; use serde_derive::Deserialize; use serde_with::serde_as; use thiserror_ext::AsReport; +use tonic::async_trait; use tracing::warn; use with_options::WithOptions; +use super::decouple_checkpoint_log_sink::DecoupleCheckpointLogSinkerOf; +use super::writer::SinkWriter; use super::{DummySinkCommitCoordinator, SinkWriterParam}; +use crate::deserialize_optional_u64_from_string; use crate::error::ConnectorResult; use crate::sink::catalog::desc::SinkDesc; -use crate::sink::log_store::DeliveryFutureManagerAddFuture; -use crate::sink::writer::{ - AsyncTruncateLogSinkerOf, AsyncTruncateSinkWriter, AsyncTruncateSinkWriterExt, -}; use crate::sink::{ Result, Sink, SinkError, SinkParam, SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, SINK_TYPE_UPSERT, }; @@ -48,7 +51,6 @@ const QUERY_ENGINE: &str = const QUERY_COLUMN: &str = "select distinct ?fields from system.columns where database = ? and table = ? order by ?"; pub const CLICKHOUSE_SINK: &str = "clickhouse"; -const BUFFER_SIZE: usize = 1024; #[derive(Deserialize, Debug, Clone, WithOptions)] pub struct ClickHouseCommon { @@ -62,23 +64,30 @@ pub struct ClickHouseCommon { pub database: String, #[serde(rename = "clickhouse.table")] pub table: String, + #[serde(rename = "clickhouse.delete.column")] + pub delete_column: Option, + /// Commit every n(>0) checkpoints, if n is not set, we will commit every checkpoint. + #[serde(default, deserialize_with = "deserialize_optional_u64_from_string")] + pub commit_checkpoint_interval: Option, } #[allow(clippy::enum_variant_names)] #[derive(Debug)] enum ClickHouseEngine { MergeTree, - ReplacingMergeTree, + ReplacingMergeTree(Option), SummingMergeTree, AggregatingMergeTree, CollapsingMergeTree(String), VersionedCollapsingMergeTree(String), GraphiteMergeTree, ReplicatedMergeTree, - ReplicatedReplacingMergeTree, + ReplicatedReplacingMergeTree(Option), ReplicatedSummingMergeTree, ReplicatedAggregatingMergeTree, + #[expect(dead_code)] ReplicatedCollapsingMergeTree(String), + #[expect(dead_code)] ReplicatedVersionedCollapsingMergeTree(String), ReplicatedGraphiteMergeTree, } @@ -93,6 +102,24 @@ impl ClickHouseEngine { ) } + pub fn is_delete_replacing_engine(&self) -> bool { + match self { + ClickHouseEngine::ReplacingMergeTree(delete_col) => delete_col.is_some(), + ClickHouseEngine::ReplicatedReplacingMergeTree(delete_col) => delete_col.is_some(), + _ => false, + } + } + + pub fn get_delete_col(&self) -> Option { + match self { + ClickHouseEngine::ReplacingMergeTree(Some(delete_col)) => Some(delete_col.to_string()), + ClickHouseEngine::ReplicatedReplacingMergeTree(Some(delete_col)) => { + Some(delete_col.to_string()) + } + _ => None, + } + } + pub fn get_sign_name(&self) -> Option { match self { ClickHouseEngine::CollapsingMergeTree(sign_name) => Some(sign_name.to_string()), @@ -109,10 +136,16 @@ impl ClickHouseEngine { } } - pub fn from_query_engine(engine_name: &ClickhouseQueryEngine) -> Result { + pub fn from_query_engine( + engine_name: &ClickhouseQueryEngine, + config: &ClickHouseConfig, + ) -> Result { match engine_name.engine.as_str() { "MergeTree" => Ok(ClickHouseEngine::MergeTree), - "ReplacingMergeTree" => Ok(ClickHouseEngine::ReplacingMergeTree), + "ReplacingMergeTree" => { + let delete_column = config.common.delete_column.clone(); + Ok(ClickHouseEngine::ReplacingMergeTree(delete_column)) + } "SummingMergeTree" => Ok(ClickHouseEngine::SummingMergeTree), "AggregatingMergeTree" => Ok(ClickHouseEngine::AggregatingMergeTree), // VersionedCollapsingMergeTree(sign_name,"a") @@ -145,7 +178,12 @@ impl ClickHouseEngine { } "GraphiteMergeTree" => Ok(ClickHouseEngine::GraphiteMergeTree), "ReplicatedMergeTree" => Ok(ClickHouseEngine::ReplicatedMergeTree), - "ReplicatedReplacingMergeTree" => Ok(ClickHouseEngine::ReplicatedReplacingMergeTree), + "ReplicatedReplacingMergeTree" => { + let delete_column = config.common.delete_column.clone(); + Ok(ClickHouseEngine::ReplicatedReplacingMergeTree( + delete_column, + )) + } "ReplicatedSummingMergeTree" => Ok(ClickHouseEngine::ReplicatedSummingMergeTree), "ReplicatedAggregatingMergeTree" => { Ok(ClickHouseEngine::ReplicatedAggregatingMergeTree) @@ -220,7 +258,7 @@ pub struct ClickHouseSink { } impl ClickHouseConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(properties).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -241,7 +279,7 @@ impl TryFrom for ClickHouseSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = ClickHouseConfig::from_hashmap(param.properties)?; + let config = ClickHouseConfig::from_btreemap(param.properties)?; Ok(Self { config, schema, @@ -261,7 +299,7 @@ impl ClickHouseSink { .collect(); if rw_fields_name.len().gt(&clickhouse_columns_desc.len()) { - return Err(SinkError::ClickHouse("The nums of the RisingWave column must be greater than/equal to the length of the Clickhouse column".to_string())); + return Err(SinkError::ClickHouse("The columns of the sink must be equal to or a superset of the target table's columns.".to_string())); } for i in rw_fields_name { @@ -374,14 +412,30 @@ impl ClickHouseSink { } impl Sink for ClickHouseSink { type Coordinator = DummySinkCommitCoordinator; - type LogSinker = AsyncTruncateLogSinkerOf; + type LogSinker = DecoupleCheckpointLogSinkerOf; const SINK_NAME: &'static str = CLICKHOUSE_SINK; fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + let config_decouple = if let Some(interval) = + desc.properties.get("commit_checkpoint_interval") + && interval.parse::().unwrap_or(0) > 1 + { + true + } else { + false + }; + match user_specified { - SinkDecouple::Default => Ok(desc.sink_type.is_append_only()), - SinkDecouple::Disable => Ok(false), + SinkDecouple::Default => Ok(config_decouple), + SinkDecouple::Disable => { + if config_decouple { + return Err(SinkError::Config(anyhow!( + "config conflict: Clickhouse config `commit_checkpoint_interval` larger than 1 means that sink decouple must be enabled, but session config sink_decouple is disabled" + ))); + } + Ok(false) + } SinkDecouple::Enable => Ok(true), } } @@ -399,34 +453,59 @@ impl Sink for ClickHouseSink { let (clickhouse_column, clickhouse_engine) = query_column_engine_from_ck(client, &self.config).await?; - if !self.is_append_only && !clickhouse_engine.is_collapsing_engine() { - return Err(SinkError::ClickHouse( - "If you want to use upsert, please modify your engine is `VersionedCollapsingMergeTree` or `CollapsingMergeTree` in ClickHouse".to_owned())); + if !self.is_append_only + && !clickhouse_engine.is_collapsing_engine() + && !clickhouse_engine.is_delete_replacing_engine() + { + return match clickhouse_engine { + ClickHouseEngine::ReplicatedReplacingMergeTree(None) | ClickHouseEngine::ReplacingMergeTree(None) => { + Err(SinkError::ClickHouse("To enable upsert with a `ReplacingMergeTree`, you must set a `clickhouse.delete.column` to the UInt8 column in ClickHouse used to signify deletes. See https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/replacingmergetree#is_deleted for more information".to_owned())) + } + _ => Err(SinkError::ClickHouse("If you want to use upsert, please use either `VersionedCollapsingMergeTree`, `CollapsingMergeTree` or the `ReplacingMergeTree` in ClickHouse".to_owned())) + }; } self.check_column_name_and_type(&clickhouse_column)?; if !self.is_append_only { self.check_pk_match(&clickhouse_column)?; } + + if self.config.common.commit_checkpoint_interval == Some(0) { + return Err(SinkError::Config(anyhow!( + "commit_checkpoint_interval must be greater than 0" + ))); + } Ok(()) } - async fn new_log_sinker(&self, _writer_param: SinkWriterParam) -> Result { - Ok(ClickHouseSinkWriter::new( + async fn new_log_sinker(&self, writer_param: SinkWriterParam) -> Result { + let writer = ClickHouseSinkWriter::new( self.config.clone(), self.schema.clone(), self.pk_indices.clone(), self.is_append_only, ) - .await? - .into_log_sinker(usize::MAX)) + .await?; + let commit_checkpoint_interval = + NonZeroU64::new(self.config.common.commit_checkpoint_interval.unwrap_or(1)).expect( + "commit_checkpoint_interval should be greater than 0, and it should be checked in config validation", + ); + + Ok(DecoupleCheckpointLogSinkerOf::new( + writer, + writer_param.sink_metrics, + commit_checkpoint_interval, + )) } } pub struct ClickHouseSinkWriter { pub config: ClickHouseConfig, + #[expect(dead_code)] schema: Schema, + #[expect(dead_code)] pk_indices: Vec, client: ClickHouseClient, + #[expect(dead_code)] is_append_only: bool, // Save some features of the clickhouse column type column_correct_vec: Vec, @@ -467,6 +546,9 @@ impl ClickHouseSinkWriter { if let Some(sign) = clickhouse_engine.get_sign_name() { rw_fields_name_after_calibration.push(sign); } + if let Some(delete_col) = clickhouse_engine.get_delete_col() { + rw_fields_name_after_calibration.push(delete_col); + } Ok(Self { config, schema, @@ -559,21 +641,35 @@ impl ClickHouseSinkWriter { } match op { Op::Insert | Op::UpdateInsert => { - if self.clickhouse_engine.get_sign_name().is_some() { + if self.clickhouse_engine.is_collapsing_engine() { clickhouse_filed_vec.push(ClickHouseFieldWithNull::WithoutSome( ClickHouseField::Int8(1), )); } + if self.clickhouse_engine.is_delete_replacing_engine() { + clickhouse_filed_vec.push(ClickHouseFieldWithNull::WithoutSome( + ClickHouseField::Int8(0), + )) + } } Op::Delete | Op::UpdateDelete => { - if !self.clickhouse_engine.is_collapsing_engine() { + if !self.clickhouse_engine.is_collapsing_engine() + && !self.clickhouse_engine.is_delete_replacing_engine() + { return Err(SinkError::ClickHouse( "Clickhouse engine don't support upsert".to_string(), )); } - clickhouse_filed_vec.push(ClickHouseFieldWithNull::WithoutSome( - ClickHouseField::Int8(-1), - )) + if self.clickhouse_engine.is_collapsing_engine() { + clickhouse_filed_vec.push(ClickHouseFieldWithNull::WithoutSome( + ClickHouseField::Int8(-1), + )); + } + if self.clickhouse_engine.is_delete_replacing_engine() { + clickhouse_filed_vec.push(ClickHouseFieldWithNull::WithoutSome( + ClickHouseField::Int8(1), + )) + } } } let clickhouse_column = ClickHouseColumn { @@ -589,23 +685,30 @@ impl ClickHouseSinkWriter { } } -impl AsyncTruncateSinkWriter for ClickHouseSinkWriter { - async fn write_chunk<'a>( - &'a mut self, - chunk: StreamChunk, - _add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>, - ) -> Result<()> { +#[async_trait] +impl SinkWriter for ClickHouseSinkWriter { + async fn write_batch(&mut self, chunk: StreamChunk) -> Result<()> { self.write(chunk).await } + async fn begin_epoch(&mut self, _epoch: u64) -> Result<()> { + Ok(()) + } + + async fn abort(&mut self) -> Result<()> { + Ok(()) + } + async fn barrier(&mut self, is_checkpoint: bool) -> Result<()> { - if let Some(inserter) = self.inserter.take() - && is_checkpoint - { + if is_checkpoint && let Some(inserter) = self.inserter.take() { inserter.end().await?; } Ok(()) } + + async fn update_vnode_bitmap(&mut self, _vnode_bitmap: Arc) -> Result<()> { + Ok(()) + } } #[derive(ClickHouseRow, Deserialize, Clone)] @@ -617,6 +720,7 @@ struct SystemColumn { #[derive(ClickHouseRow, Deserialize)] struct ClickhouseQueryEngine { + #[expect(dead_code)] name: String, engine: String, create_table_query: String, @@ -650,11 +754,16 @@ async fn query_column_engine_from_ck( } let clickhouse_engine = - ClickHouseEngine::from_query_engine(clickhouse_engine.first().unwrap())?; + ClickHouseEngine::from_query_engine(clickhouse_engine.first().unwrap(), config)?; if let Some(sign) = &clickhouse_engine.get_sign_name() { clickhouse_column.retain(|a| sign.ne(&a.name)) } + + if let Some(delete_col) = &clickhouse_engine.get_delete_col() { + clickhouse_column.retain(|a| delete_col.ne(&a.name)) + } + Ok((clickhouse_column, clickhouse_engine)) } diff --git a/src/connector/src/sink/coordinate.rs b/src/connector/src/sink/coordinate.rs index f2bc4f4b3b362..c069167870101 100644 --- a/src/connector/src/sink/coordinate.rs +++ b/src/connector/src/sink/coordinate.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use anyhow::anyhow; use risingwave_common::array::StreamChunk; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_pb::connector_service::SinkMetadata; use risingwave_rpc_client::CoordinatorStreamHandle; use tracing::warn; diff --git a/src/connector/src/sink/iceberg/log_sink.rs b/src/connector/src/sink/decouple_checkpoint_log_sink.rs similarity index 89% rename from src/connector/src/sink/iceberg/log_sink.rs rename to src/connector/src/sink/decouple_checkpoint_log_sink.rs index fec6285774bbe..26576cf3e3666 100644 --- a/src/connector/src/sink/iceberg/log_sink.rs +++ b/src/connector/src/sink/decouple_checkpoint_log_sink.rs @@ -21,13 +21,16 @@ use crate::sink::log_store::{LogStoreReadItem, TruncateOffset}; use crate::sink::writer::SinkWriter; use crate::sink::{LogSinker, Result, SinkLogReader, SinkMetrics}; -pub struct IcebergLogSinkerOf { +/// The `LogSinker` implementation used for commit-decoupled sinks (such as `Iceberg`, `DeltaLake` and `StarRocks`). +/// The concurrent/frequent commit capability of these sinks is poor, so by leveraging the decoupled log reader, +/// we delay the checkpoint barrier to make commits less frequent. +pub struct DecoupleCheckpointLogSinkerOf { writer: W, sink_metrics: SinkMetrics, commit_checkpoint_interval: NonZeroU64, } -impl IcebergLogSinkerOf { +impl DecoupleCheckpointLogSinkerOf { /// Create a log sinker with a commit checkpoint interval. The sinker should be used with a /// decouple log reader `KvLogStoreReader`. pub fn new( @@ -35,7 +38,7 @@ impl IcebergLogSinkerOf { sink_metrics: SinkMetrics, commit_checkpoint_interval: NonZeroU64, ) -> Self { - IcebergLogSinkerOf { + DecoupleCheckpointLogSinkerOf { writer, sink_metrics, commit_checkpoint_interval, @@ -44,8 +47,8 @@ impl IcebergLogSinkerOf { } #[async_trait] -impl> LogSinker for IcebergLogSinkerOf { - async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result<()> { +impl> LogSinker for DecoupleCheckpointLogSinkerOf { + async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result { let mut sink_writer = self.writer; let sink_metrics = self.sink_metrics; #[derive(Debug)] @@ -123,9 +126,7 @@ impl> LogSinker for IcebergLogSinkerOf { sink_metrics .sink_commit_duration_metrics .observe(start_time.elapsed().as_millis() as f64); - log_reader - .truncate(TruncateOffset::Barrier { epoch }) - .await?; + log_reader.truncate(TruncateOffset::Barrier { epoch })?; current_checkpoint = 0; } else { sink_writer.barrier(false).await?; diff --git a/src/connector/src/sink/deltalake.rs b/src/connector/src/sink/deltalake.rs index 388a75c264a71..43e9d2c98cf6c 100644 --- a/src/connector/src/sink/deltalake.rs +++ b/src/connector/src/sink/deltalake.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use core::num::NonZeroU64; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use anyhow::{anyhow, Context}; @@ -25,10 +26,12 @@ use deltalake::table::builder::s3_storage_options::{ }; use deltalake::writer::{DeltaWriter, RecordBatchWriter}; use deltalake::DeltaTable; -use risingwave_common::array::{to_deltalake_record_batch_with_schema, StreamChunk}; +use risingwave_common::array::arrow::DeltaLakeConvert; +use risingwave_common::array::StreamChunk; use risingwave_common::bail; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::Schema; +use risingwave_common::session_config::sink_decouple::SinkDecouple; use risingwave_common::types::DataType; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_pb::connector_service::sink_metadata::Metadata::Serialized; @@ -38,13 +41,15 @@ use serde_derive::{Deserialize, Serialize}; use serde_with::serde_as; use with_options::WithOptions; +use super::catalog::desc::SinkDesc; use super::coordinate::CoordinatedSinkWriter; -use super::writer::{LogSinkerOf, SinkWriter}; +use super::decouple_checkpoint_log_sink::DecoupleCheckpointLogSinkerOf; +use super::writer::SinkWriter; use super::{ Result, Sink, SinkCommitCoordinator, SinkError, SinkParam, SinkWriterParam, SINK_TYPE_APPEND_ONLY, SINK_USER_FORCE_APPEND_ONLY_OPTION, }; -use crate::sink::writer::SinkWriterExt; +use crate::deserialize_optional_u64_from_string; pub const DELTALAKE_SINK: &str = "deltalake"; pub const DEFAULT_REGION: &str = "us-east-1"; @@ -64,6 +69,9 @@ pub struct DeltaLakeCommon { pub s3_endpoint: Option, #[serde(rename = "gcs.service.account")] pub gcs_service_account: Option, + /// Commit every n(>0) checkpoints, if n is not set, we will commit every checkpoint. + #[serde(default, deserialize_with = "deserialize_optional_u64_from_string")] + pub commit_checkpoint_interval: Option, } impl DeltaLakeCommon { pub async fn create_deltalake_client(&self) -> Result { @@ -149,7 +157,7 @@ pub struct DeltaLakeConfig { } impl DeltaLakeConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::( serde_json::to_value(properties).map_err(|e| SinkError::DeltaLake(e.into()))?, ) @@ -268,10 +276,34 @@ fn check_field_type(rw_data_type: &DataType, dl_data_type: &DeltaLakeDataType) - impl Sink for DeltaLakeSink { type Coordinator = DeltaLakeSinkCommitter; - type LogSinker = LogSinkerOf>; + type LogSinker = DecoupleCheckpointLogSinkerOf>; const SINK_NAME: &'static str = DELTALAKE_SINK; + fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + let config_decouple = if let Some(interval) = + desc.properties.get("commit_checkpoint_interval") + && interval.parse::().unwrap_or(0) > 1 + { + true + } else { + false + }; + + match user_specified { + SinkDecouple::Default => Ok(config_decouple), + SinkDecouple::Disable => { + if config_decouple { + return Err(SinkError::Config(anyhow!( + "config conflict: DeltaLake config `commit_checkpoint_interval` larger than 1 means that sink decouple must be enabled, but session config sink_decouple is disabled" + ))); + } + Ok(false) + } + SinkDecouple::Enable => Ok(true), + } + } + async fn new_log_sinker(&self, writer_param: SinkWriterParam) -> Result { let inner = DeltaLakeSinkWriter::new( self.config.clone(), @@ -279,7 +311,7 @@ impl Sink for DeltaLakeSink { self.param.downstream_pk.clone(), ) .await?; - Ok(CoordinatedSinkWriter::new( + let writer = CoordinatedSinkWriter::new( writer_param .meta_client .expect("should have meta client") @@ -293,8 +325,18 @@ impl Sink for DeltaLakeSink { })?, inner, ) - .await? - .into_log_sinker(writer_param.sink_metrics)) + .await?; + + let commit_checkpoint_interval = + NonZeroU64::new(self.config.common.commit_checkpoint_interval.unwrap_or(1)).expect( + "commit_checkpoint_interval should be greater than 0, and it should be checked in config validation", + ); + + Ok(DecoupleCheckpointLogSinkerOf::new( + writer, + writer_param.sink_metrics, + commit_checkpoint_interval, + )) } async fn validate(&self) -> Result<()> { @@ -338,6 +380,11 @@ impl Sink for DeltaLakeSink { ))); } } + if self.config.common.commit_checkpoint_interval == Some(0) { + return Err(SinkError::Config(anyhow!( + "commit_checkpoint_interval must be greater than 0" + ))); + } Ok(()) } @@ -352,17 +399,20 @@ impl TryFrom for DeltaLakeSink { type Error = SinkError; fn try_from(param: SinkParam) -> std::result::Result { - let config = DeltaLakeConfig::from_hashmap(param.properties.clone())?; + let config = DeltaLakeConfig::from_btreemap(param.properties.clone())?; DeltaLakeSink::new(config, param) } } pub struct DeltaLakeSinkWriter { pub config: DeltaLakeConfig, + #[expect(dead_code)] schema: Schema, + #[expect(dead_code)] pk_indices: Vec, writer: RecordBatchWriter, dl_schema: Arc, + #[expect(dead_code)] dl_table: DeltaTable, } @@ -388,7 +438,8 @@ impl DeltaLakeSinkWriter { } async fn write(&mut self, chunk: StreamChunk) -> Result<()> { - let a = to_deltalake_record_batch_with_schema(self.dl_schema.clone(), &chunk) + let a = DeltaLakeConvert + .to_record_batch(self.dl_schema.clone(), &chunk) .context("convert record batch error") .map_err(SinkError::DeltaLake)?; self.writer.write(a).await?; @@ -530,7 +581,7 @@ impl DeltaLakeWriteResult { mod test { use deltalake::kernel::DataType as SchemaDataType; use deltalake::operations::create::CreateBuilder; - use maplit::hashmap; + use maplit::btreemap; use risingwave_common::array::{Array, I32Array, Op, StreamChunk, Utf8Array}; use risingwave_common::catalog::{Field, Schema}; @@ -551,7 +602,7 @@ mod test { .await .unwrap(); - let properties = hashmap! { + let properties = btreemap! { "connector".to_string() => "deltalake".to_string(), "force_append_only".to_string() => "true".to_string(), "type".to_string() => "append-only".to_string(), @@ -573,7 +624,7 @@ mod test { }, ]); - let deltalake_config = DeltaLakeConfig::from_hashmap(properties).unwrap(); + let deltalake_config = DeltaLakeConfig::from_btreemap(properties).unwrap(); let deltalake_table = deltalake_config .common .create_deltalake_client() diff --git a/src/connector/src/sink/doris.rs b/src/connector/src/sink/doris.rs index 75283f200c254..35c438a534992 100644 --- a/src/connector/src/sink/doris.rs +++ b/src/connector/src/sink/doris.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use anyhow::{anyhow, Context}; @@ -21,7 +21,7 @@ use base64::engine::general_purpose; use base64::Engine; use bytes::{BufMut, Bytes, BytesMut}; use risingwave_common::array::{Op, StreamChunk}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::Schema; use risingwave_common::types::DataType; use serde::Deserialize; @@ -54,6 +54,8 @@ pub struct DorisCommon { pub database: String, #[serde(rename = "doris.table")] pub table: String, + #[serde(rename = "doris.partial_update")] + pub partial_update: Option, } impl DorisCommon { @@ -77,7 +79,7 @@ pub struct DorisConfig { pub r#type: String, // accept "append-only" or "upsert" } impl DorisConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(properties).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -125,8 +127,11 @@ impl DorisSink { .collect(); let rw_fields_name = self.schema.fields(); - if rw_fields_name.len().ne(&doris_columns_desc.len()) { - return Err(SinkError::Doris("The length of the RisingWave column must be equal to the length of the doris column".to_string())); + if rw_fields_name.len() > doris_columns_desc.len() { + return Err(SinkError::Doris( + "The columns of the sink must be equal to or a superset of the target table's columns." + .to_string(), + )); } for i in rw_fields_name { @@ -225,7 +230,9 @@ impl Sink for DorisSink { pub struct DorisSinkWriter { pub config: DorisConfig, + #[expect(dead_code)] schema: Schema, + #[expect(dead_code)] pk_indices: Vec, inserter_inner_builder: InserterInnerBuilder, is_append_only: bool, @@ -238,7 +245,7 @@ impl TryFrom for DorisSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = DorisConfig::from_hashmap(param.properties)?; + let config = DorisConfig::from_btreemap(param.properties)?; DorisSink::new( config, schema, @@ -271,6 +278,7 @@ impl DorisSinkWriter { .add_common_header() .set_user_password(config.common.user.clone(), config.common.password.clone()) .add_json_format() + .set_partial_columns(config.common.partial_update.clone()) .add_read_json_by_line(); let header = if !is_append_only { header_builder.add_hidden_column().build() diff --git a/src/connector/src/sink/doris_starrocks_connector.rs b/src/connector/src/sink/doris_starrocks_connector.rs index 6c045c63beb47..fb0a37572710e 100644 --- a/src/connector/src/sink/doris_starrocks_connector.rs +++ b/src/connector/src/sink/doris_starrocks_connector.rs @@ -17,12 +17,13 @@ use core::time::Duration; use std::collections::HashMap; use std::convert::Infallible; -use anyhow::Context; +use anyhow::{anyhow, Context}; use base64::engine::general_purpose; use base64::Engine; use bytes::{BufMut, Bytes, BytesMut}; use futures::StreamExt; -use reqwest::{redirect, Body, Client, RequestBuilder, StatusCode}; +use reqwest::header::{HeaderName, HeaderValue}; +use reqwest::{redirect, Body, Client, Method, Request, RequestBuilder, Response, StatusCode}; use tokio::sync::mpsc::UnboundedSender; use tokio::task::JoinHandle; use url::Url; @@ -32,14 +33,12 @@ use super::{Result, SinkError}; const BUFFER_SIZE: usize = 64 * 1024; const MIN_CHUNK_SIZE: usize = BUFFER_SIZE - 1024; pub(crate) const DORIS_SUCCESS_STATUS: [&str; 2] = ["Success", "Publish Timeout"]; +pub(crate) const STARROCKS_SUCCESS_STATUS: [&str; 1] = ["OK"]; pub(crate) const DORIS_DELETE_SIGN: &str = "__DORIS_DELETE_SIGN__"; pub(crate) const STARROCKS_DELETE_SIGN: &str = "__op"; -const SEND_CHUNK_TIMEOUT: Duration = Duration::from_secs(10); const WAIT_HANDDLE_TIMEOUT: Duration = Duration::from_secs(10); pub(crate) const POOL_IDLE_TIMEOUT: Duration = Duration::from_secs(30); -const DORIS: &str = "doris"; -const STARROCKS: &str = "starrocks"; const LOCALHOST: &str = "localhost"; const LOCALHOST_IP: &str = "127.0.0.1"; pub struct HeaderBuilder { @@ -143,6 +142,7 @@ impl HeaderBuilder { self } + /// Only use in Starrocks pub fn set_partial_update(mut self, partial_update: Option) -> Self { self.header.insert( "partial_update".to_string(), @@ -151,14 +151,86 @@ impl HeaderBuilder { self } + /// Only use in Doris + pub fn set_partial_columns(mut self, partial_columns: Option) -> Self { + self.header.insert( + "partial_columns".to_string(), + partial_columns.unwrap_or_else(|| "false".to_string()), + ); + self + } + + /// Only used in Starrocks Transaction API + pub fn set_db(mut self, db: String) -> Self { + self.header.insert("db".to_string(), db); + self + } + + /// Only used in Starrocks Transaction API + pub fn set_table(mut self, table: String) -> Self { + self.header.insert("table".to_string(), table); + self + } + pub fn build(self) -> HashMap { self.header } } +/// Try getting BE url from a redirected response, returning `Ok(None)` indicates this request does +/// not redirect. +/// +/// The reason we handle the redirection manually is that if we let `reqwest` handle the redirection +/// automatically, it will remove sensitive headers (such as Authorization) during the redirection, +/// and there's no way to prevent this behavior. +fn try_get_be_url(resp: &Response, fe_host: String) -> Result> { + match resp.status() { + StatusCode::TEMPORARY_REDIRECT => { + let be_url = resp + .headers() + .get("location") + .ok_or_else(|| { + SinkError::DorisStarrocksConnect(anyhow!("Can't get doris BE url in header",)) + })? + .to_str() + .context("Can't get doris BE url in header") + .map_err(SinkError::DorisStarrocksConnect)? + .to_string(); + + let mut parsed_be_url = Url::parse(&be_url) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + + if fe_host != LOCALHOST && fe_host != LOCALHOST_IP { + let be_host = parsed_be_url.host_str().ok_or_else(|| { + SinkError::DorisStarrocksConnect(anyhow!("Can't get be host from url")) + })?; + + if be_host == LOCALHOST || be_host == LOCALHOST_IP { + // if be host is 127.0.0.1, we may can't connect to it directly, + // so replace it with fe host + parsed_be_url + .set_host(Some(fe_host.as_str())) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + } + } + Ok(Some(parsed_be_url)) + } + StatusCode::OK => { + // Some of the `StarRocks` transactional APIs will respond directly from FE. For example, + // the request to `/api/transaction/commit` endpoint does not seem to redirect to BE. + // In this case, the request should be treated as finished. + Ok(None) + } + _ => Err(SinkError::DorisStarrocksConnect(anyhow!( + "Can't get doris BE url", + ))), + } +} + pub struct InserterInnerBuilder { url: String, header: HashMap, + #[expect(dead_code)] sender: Option, fe_host: String, } @@ -170,11 +242,9 @@ impl InserterInnerBuilder { header: HashMap, ) -> Result { let fe_host = Url::parse(&url) - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))? + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))? .host_str() - .ok_or_else(|| { - SinkError::DorisStarrocksConnect(anyhow::anyhow!("Can't get fe host from url")) - })? + .ok_or_else(|| SinkError::DorisStarrocksConnect(anyhow!("Can't get fe host from url")))? .to_string(); let url = format!("{}/api/{}/{}/_stream_load", url, db, table); @@ -205,73 +275,45 @@ impl InserterInnerBuilder { let resp = builder .send() .await - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?; - // TODO: shall we let `reqwest` handle the redirect? - let mut be_url = if resp.status() == StatusCode::TEMPORARY_REDIRECT { - resp.headers() - .get("location") - .ok_or_else(|| { - SinkError::DorisStarrocksConnect(anyhow::anyhow!( - "Can't get doris BE url in header", - )) - })? - .to_str() - .context("Can't get doris BE url in header") - .map_err(SinkError::DorisStarrocksConnect)? - .to_string() - } else { - return Err(SinkError::DorisStarrocksConnect(anyhow::anyhow!( - "Can't get doris BE url", - ))); - }; + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; - if self.fe_host != LOCALHOST && self.fe_host != LOCALHOST_IP { - let mut parsed_be_url = - Url::parse(&be_url).map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?; - let be_host = parsed_be_url.host_str().ok_or_else(|| { - SinkError::DorisStarrocksConnect(anyhow::anyhow!("Can't get be host from url")) - })?; - - if be_host == LOCALHOST || be_host == LOCALHOST_IP { - // if be host is 127.0.0.1, we may can't connect to it directly, - // so replace it with fe host - parsed_be_url - .set_host(Some(self.fe_host.as_str())) - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?; - be_url = parsed_be_url.as_str().into(); - } - } + let be_url = try_get_be_url(&resp, self.fe_host.clone())? + .ok_or_else(|| SinkError::DorisStarrocksConnect(anyhow!("Can't get doris BE url",)))?; let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); let body = Body::wrap_stream( tokio_stream::wrappers::UnboundedReceiverStream::new(receiver).map(Ok::<_, Infallible>), ); - let builder = self.build_request(be_url).body(body); + let builder = self.build_request(be_url.into()).body(body); let handle: JoinHandle>> = tokio::spawn(async move { let response = builder .send() .await - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?; + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; let status = response.status(); let raw = response .bytes() .await - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))? + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))? .into(); if status == StatusCode::OK { Ok(raw) } else { - Err(SinkError::DorisStarrocksConnect(anyhow::anyhow!( + let response_body = String::from_utf8(raw).map_err(|err| { + SinkError::DorisStarrocksConnect( + anyhow!(err).context("failed to parse response body"), + ) + })?; + Err(SinkError::DorisStarrocksConnect(anyhow!( "Failed connection {:?},{:?}", status, - String::from_utf8(raw) - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))? + response_body ))) } }); - Ok(InserterInner::new(sender, handle)) + Ok(InserterInner::new(sender, handle, WAIT_HANDDLE_TIMEOUT)) } } @@ -281,13 +323,19 @@ pub struct InserterInner { sender: Option, join_handle: Option>>>, buffer: BytesMut, + stream_load_http_timeout: Duration, } impl InserterInner { - pub fn new(sender: Sender, join_handle: JoinHandle>>) -> Self { + pub fn new( + sender: Sender, + join_handle: JoinHandle>>, + stream_load_http_timeout: Duration, + ) -> Self { Self { sender: Some(sender), join_handle: Some(join_handle), buffer: BytesMut::with_capacity(BUFFER_SIZE), + stream_load_http_timeout, } } @@ -302,9 +350,7 @@ impl InserterInner { self.sender.take(); self.wait_handle().await?; - Err(SinkError::DorisStarrocksConnect(anyhow::anyhow!( - "channel closed" - ))) + Err(SinkError::DorisStarrocksConnect(anyhow!("channel closed"))) } else { Ok(()) } @@ -319,13 +365,15 @@ impl InserterInner { } async fn wait_handle(&mut self) -> Result> { - let res = - match tokio::time::timeout(WAIT_HANDDLE_TIMEOUT, self.join_handle.as_mut().unwrap()) - .await - { - Ok(res) => res.map_err(|err| SinkError::DorisStarrocksConnect(err.into()))??, - Err(err) => return Err(SinkError::DorisStarrocksConnect(err.into())), - }; + let res = match tokio::time::timeout( + self.stream_load_http_timeout, + self.join_handle.as_mut().unwrap(), + ) + .await + { + Ok(res) => res.map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))??, + Err(err) => return Err(SinkError::DorisStarrocksConnect(anyhow!(err))), + }; Ok(res) } @@ -337,3 +385,235 @@ impl InserterInner { self.wait_handle().await } } + +pub struct MetaRequestSender { + client: Client, + request: Request, + fe_host: String, +} + +impl MetaRequestSender { + pub fn new(client: Client, request: Request, fe_host: String) -> Self { + Self { + client, + request, + fe_host, + } + } + + /// Send the request and handle redirection if any. + /// The reason we handle the redirection manually is that if we let `reqwest` handle the redirection + /// automatically, it will remove sensitive headers (such as Authorization) during the redirection, + /// and there's no way to prevent this behavior. + /// + /// Another interesting point is that some of the `StarRocks` transactional APIs will respond directly from FE. + /// For example, the request to `/api/transaction/commit` endpoint does not seem to redirect to BE. + pub async fn send(self) -> Result { + let request = self.request; + let mut request_for_redirection = request + .try_clone() + .ok_or_else(|| SinkError::DorisStarrocksConnect(anyhow!("Can't clone request")))?; + + let resp = self + .client + .execute(request) + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + + let be_url = try_get_be_url(&resp, self.fe_host)?; + + match be_url { + Some(be_url) => { + *request_for_redirection.url_mut() = be_url; + + self.client + .execute(request_for_redirection) + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))? + .bytes() + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err))) + } + None => resp + .bytes() + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err))), + } + } +} + +pub struct StarrocksTxnRequestBuilder { + url_begin: String, + url_load: String, + url_prepare: String, + url_commit: String, + url_rollback: String, + header: HashMap, + fe_host: String, + stream_load_http_timeout: Duration, + // The `reqwest` crate suggests us reuse the Client, and we don't need make it Arc, because it + // already uses an Arc internally. + client: Client, +} + +impl StarrocksTxnRequestBuilder { + pub fn new( + url: String, + header: HashMap, + stream_load_http_timeout_ms: u64, + ) -> Result { + let fe_host = Url::parse(&url) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))? + .host_str() + .ok_or_else(|| SinkError::DorisStarrocksConnect(anyhow!("Can't get fe host from url")))? + .to_string(); + + let url_begin = format!("{}/api/transaction/begin", url); + let url_load = format!("{}/api/transaction/load", url); + let url_prepare = format!("{}/api/transaction/prepare", url); + let url_commit = format!("{}/api/transaction/commit", url); + let url_rollback = format!("{}/api/transaction/rollback", url); + + let stream_load_http_timeout = Duration::from_millis(stream_load_http_timeout_ms); + + let client = Client::builder() + .pool_idle_timeout(POOL_IDLE_TIMEOUT) + .redirect(redirect::Policy::none()) + .build() + .unwrap(); + + Ok(Self { + url_begin, + url_load, + url_prepare, + url_commit, + url_rollback, + header, + fe_host, + stream_load_http_timeout, + client, + }) + } + + fn build_request(&self, uri: String, method: Method, label: String) -> Result { + let parsed_url = + Url::parse(&uri).map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + let mut request = Request::new(method, parsed_url); + + if uri != self.url_load { + // Set timeout for non-load requests; load requests' timeout is controlled by `tokio::timeout` + *request.timeout_mut() = Some(self.stream_load_http_timeout); + } + + let header = request.headers_mut(); + for (k, v) in &self.header { + header.insert( + HeaderName::try_from(k) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?, + HeaderValue::try_from(v) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?, + ); + } + header.insert( + "label", + HeaderValue::try_from(label) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?, + ); + + Ok(request) + } + + pub fn build_begin_request_sender(&self, label: String) -> Result { + let request = self.build_request(self.url_begin.clone(), Method::POST, label)?; + Ok(MetaRequestSender::new( + self.client.clone(), + request, + self.fe_host.clone(), + )) + } + + pub fn build_prepare_request_sender(&self, label: String) -> Result { + let request = self.build_request(self.url_prepare.clone(), Method::POST, label)?; + Ok(MetaRequestSender::new( + self.client.clone(), + request, + self.fe_host.clone(), + )) + } + + pub fn build_commit_request_sender(&self, label: String) -> Result { + let request = self.build_request(self.url_commit.clone(), Method::POST, label)?; + Ok(MetaRequestSender::new( + self.client.clone(), + request, + self.fe_host.clone(), + )) + } + + pub fn build_rollback_request_sender(&self, label: String) -> Result { + let request = self.build_request(self.url_rollback.clone(), Method::POST, label)?; + Ok(MetaRequestSender::new( + self.client.clone(), + request, + self.fe_host.clone(), + )) + } + + pub async fn build_txn_inserter(&self, label: String) -> Result { + let request = self.build_request(self.url_load.clone(), Method::PUT, label)?; + let mut request_for_redirection = request + .try_clone() + .ok_or_else(|| SinkError::DorisStarrocksConnect(anyhow!("Can't clone request")))?; + + let resp = self + .client + .execute(request) + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + + let be_url = try_get_be_url(&resp, self.fe_host.clone())? + .ok_or_else(|| SinkError::DorisStarrocksConnect(anyhow!("Can't get doris BE url",)))?; + *request_for_redirection.url_mut() = be_url; + + let (sender, receiver) = tokio::sync::mpsc::unbounded_channel(); + let body = Body::wrap_stream( + tokio_stream::wrappers::UnboundedReceiverStream::new(receiver).map(Ok::<_, Infallible>), + ); + *request_for_redirection.body_mut() = Some(body); + + let client = self.client.clone(); + let handle: JoinHandle>> = tokio::spawn(async move { + let response = client + .execute(request_for_redirection) + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + + let status = response.status(); + let raw = response + .bytes() + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))? + .into(); + + if status == StatusCode::OK { + Ok(raw) + } else { + let response_body = String::from_utf8(raw).map_err(|err| { + SinkError::DorisStarrocksConnect( + anyhow!(err).context("failed to parse response body"), + ) + })?; + Err(SinkError::DorisStarrocksConnect(anyhow!( + "Failed connection {:?},{:?}", + status, + response_body + ))) + } + }); + Ok(InserterInner::new( + sender, + handle, + self.stream_load_http_timeout, + )) + } +} diff --git a/src/connector/src/sink/dynamodb.rs b/src/connector/src/sink/dynamodb.rs new file mode 100644 index 0000000000000..35b48c6e31faf --- /dev/null +++ b/src/connector/src/sink/dynamodb.rs @@ -0,0 +1,400 @@ +// Copyright 2024 RisingWave Labs +// +// 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::{BTreeMap, HashMap, HashSet}; + +use anyhow::{anyhow, Context}; +use aws_sdk_dynamodb as dynamodb; +use aws_sdk_dynamodb::client::Client; +use aws_smithy_types::Blob; +use dynamodb::types::{ + AttributeValue, DeleteRequest, PutRequest, ReturnConsumedCapacity, ReturnItemCollectionMetrics, + TableStatus, WriteRequest, +}; +use maplit::hashmap; +use risingwave_common::array::{Op, RowRef, StreamChunk}; +use risingwave_common::catalog::{Field, Schema}; +use risingwave_common::row::Row as _; +use risingwave_common::types::{DataType, ScalarRefImpl, ToText}; +use risingwave_common::util::iter_util::ZipEqDebug; +use serde_derive::Deserialize; +use serde_with::{serde_as, DisplayFromStr}; +use with_options::WithOptions; + +use super::log_store::DeliveryFutureManagerAddFuture; +use super::writer::{ + AsyncTruncateLogSinkerOf, AsyncTruncateSinkWriter, AsyncTruncateSinkWriterExt, +}; +use super::{DummySinkCommitCoordinator, Result, Sink, SinkError, SinkParam, SinkWriterParam}; +use crate::connector_common::AwsAuthProps; +use crate::error::ConnectorResult; + +pub const DYNAMO_DB_SINK: &str = "dynamodb"; + +#[serde_as] +#[derive(Deserialize, Debug, Clone, WithOptions)] +pub struct DynamoDbConfig { + #[serde(rename = "table", alias = "dynamodb.table")] + pub table: String, + + #[serde(rename = "dynamodb.max_batch_rows", default = "default_max_batch_rows")] + #[serde_as(as = "DisplayFromStr")] + pub max_batch_rows: usize, + + #[serde(flatten)] + pub aws_auth_props: AwsAuthProps, +} + +fn default_max_batch_rows() -> usize { + 1024 +} + +impl DynamoDbConfig { + pub async fn build_client(&self) -> ConnectorResult { + let config = &self.aws_auth_props; + let aws_config = config.build_config().await?; + + Ok(Client::new(&aws_config)) + } + + fn from_btreemap(values: BTreeMap) -> Result { + serde_json::from_value::(serde_json::to_value(values).unwrap()) + .map_err(|e| SinkError::Config(anyhow!(e))) + } +} + +#[derive(Clone, Debug)] +pub struct DynamoDbSink { + pub config: DynamoDbConfig, + schema: Schema, + pk_indices: Vec, +} + +impl Sink for DynamoDbSink { + type Coordinator = DummySinkCommitCoordinator; + type LogSinker = AsyncTruncateLogSinkerOf; + + const SINK_NAME: &'static str = DYNAMO_DB_SINK; + + async fn validate(&self) -> Result<()> { + let client = (self.config.build_client().await) + .context("validate DynamoDB sink error") + .map_err(SinkError::DynamoDb)?; + + let table_name = &self.config.table; + let output = client + .describe_table() + .table_name(table_name) + .send() + .await + .map_err(|e| anyhow!(e))?; + let Some(table) = output.table else { + return Err(SinkError::DynamoDb(anyhow!( + "table {} not found", + table_name + ))); + }; + if !matches!(table.table_status(), Some(TableStatus::Active)) { + return Err(SinkError::DynamoDb(anyhow!( + "table {} is not active", + table_name + ))); + } + let pk_set: HashSet = self + .schema + .fields() + .iter() + .enumerate() + .filter(|(k, _)| self.pk_indices.contains(k)) + .map(|(_, v)| v.name.clone()) + .collect(); + let key_schema = table.key_schema(); + + for key_element in key_schema.iter().map(|x| x.attribute_name()) { + if !pk_set.contains(key_element) { + return Err(SinkError::DynamoDb(anyhow!( + "table {} key field {} not found in schema or not primary key", + table_name, + key_element + ))); + } + } + + Ok(()) + } + + async fn new_log_sinker(&self, _writer_param: SinkWriterParam) -> Result { + Ok( + DynamoDbSinkWriter::new(self.config.clone(), self.schema.clone()) + .await? + .into_log_sinker(usize::MAX), + ) + } +} + +impl TryFrom for DynamoDbSink { + type Error = SinkError; + + fn try_from(param: SinkParam) -> std::result::Result { + let schema = param.schema(); + let config = DynamoDbConfig::from_btreemap(param.properties)?; + + Ok(Self { + config, + schema, + pk_indices: param.downstream_pk, + }) + } +} + +#[derive(Debug)] +struct DynamoDbRequest { + inner: WriteRequest, + key_items: Vec, +} + +impl DynamoDbRequest { + fn extract_pk_values(&self) -> Option> { + let key = match (&self.inner.put_request(), &self.inner.delete_request()) { + (Some(put_req), None) => &put_req.item, + (None, Some(del_req)) => &del_req.key, + _ => return None, + }; + let vs = key + .iter() + .filter(|(k, _)| self.key_items.contains(k)) + .map(|(_, v)| v.clone()) + .collect(); + Some(vs) + } +} + +struct DynamoDbPayloadWriter { + request_items: Vec, + client: Client, + table: String, + dynamodb_keys: Vec, +} + +impl DynamoDbPayloadWriter { + fn write_one_insert(&mut self, item: HashMap) { + let put_req = PutRequest::builder().set_item(Some(item)).build().unwrap(); + let req = WriteRequest::builder().put_request(put_req).build(); + self.write_one_req(req); + } + + fn write_one_delete(&mut self, key: HashMap) { + let key = key + .into_iter() + .filter(|(k, _)| self.dynamodb_keys.contains(k)) + .collect(); + let del_req = DeleteRequest::builder().set_key(Some(key)).build().unwrap(); + let req = WriteRequest::builder().delete_request(del_req).build(); + self.write_one_req(req); + } + + fn write_one_req(&mut self, req: WriteRequest) { + let r_req = DynamoDbRequest { + inner: req, + key_items: self.dynamodb_keys.clone(), + }; + if let Some(v) = r_req.extract_pk_values() { + self.request_items.retain(|item| { + !item + .extract_pk_values() + .unwrap_or_default() + .iter() + .all(|x| v.contains(x)) + }); + } + self.request_items.push(r_req); + } + + async fn write_chunk(&mut self) -> Result<()> { + if !self.request_items.is_empty() { + let table = self.table.clone(); + let req_items = std::mem::take(&mut self.request_items) + .into_iter() + .map(|r| r.inner) + .collect(); + let reqs = hashmap! { + table => req_items, + }; + self.client + .batch_write_item() + .set_request_items(Some(reqs)) + .return_consumed_capacity(ReturnConsumedCapacity::None) + .return_item_collection_metrics(ReturnItemCollectionMetrics::None) + .send() + .await + .map_err(|e| { + SinkError::DynamoDb( + anyhow!(e).context("failed to delete item from DynamoDB sink"), + ) + })?; + } + + Ok(()) + } +} + +pub struct DynamoDbSinkWriter { + max_batch_rows: usize, + payload_writer: DynamoDbPayloadWriter, + formatter: DynamoDbFormatter, +} + +impl DynamoDbSinkWriter { + pub async fn new(config: DynamoDbConfig, schema: Schema) -> Result { + let client = config.build_client().await?; + let table_name = &config.table; + let output = client + .describe_table() + .table_name(table_name) + .send() + .await + .map_err(|e| anyhow!(e))?; + let Some(table) = output.table else { + return Err(SinkError::DynamoDb(anyhow!( + "table {} not found", + table_name + ))); + }; + let dynamodb_keys = table + .key_schema + .unwrap_or_default() + .into_iter() + .map(|k| k.attribute_name) + .collect(); + + let payload_writer = DynamoDbPayloadWriter { + request_items: Vec::new(), + client, + table: config.table, + dynamodb_keys, + }; + + Ok(Self { + max_batch_rows: config.max_batch_rows, + payload_writer, + formatter: DynamoDbFormatter { schema }, + }) + } + + async fn write_chunk_inner(&mut self, chunk: StreamChunk) -> Result<()> { + for (op, row) in chunk.rows() { + let items = self.formatter.format_row(row)?; + match op { + Op::Insert | Op::UpdateInsert => { + self.payload_writer.write_one_insert(items); + } + Op::Delete => { + self.payload_writer.write_one_delete(items); + } + Op::UpdateDelete => {} + } + } + if self.payload_writer.request_items.len() >= self.max_batch_rows { + self.payload_writer.write_chunk().await?; + } + Ok(()) + } + + async fn flush(&mut self) -> Result<()> { + self.payload_writer.write_chunk().await + } +} + +impl AsyncTruncateSinkWriter for DynamoDbSinkWriter { + async fn write_chunk<'a>( + &'a mut self, + chunk: StreamChunk, + _add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>, + ) -> Result<()> { + self.write_chunk_inner(chunk).await + } + + async fn barrier(&mut self, is_checkpoint: bool) -> Result<()> { + if is_checkpoint { + self.flush().await?; + } + Ok(()) + } +} + +struct DynamoDbFormatter { + schema: Schema, +} + +impl DynamoDbFormatter { + fn format_row(&self, row: RowRef<'_>) -> Result> { + row.iter() + .zip_eq_debug((self.schema.clone()).into_fields()) + .map(|(scalar, field)| { + map_data_type(scalar, &field.data_type()).map(|attr| (field.name, attr)) + }) + .collect() + } +} + +fn map_data_type( + scalar_ref: Option>, + data_type: &DataType, +) -> Result { + let Some(scalar_ref) = scalar_ref else { + return Ok(AttributeValue::Null(true)); + }; + let attr = match data_type { + DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Int256 + | DataType::Float32 + | DataType::Float64 + | DataType::Decimal + | DataType::Serial => AttributeValue::N(scalar_ref.to_text_with_type(data_type)), + // TODO: jsonb as dynamic type (https://github.com/risingwavelabs/risingwave/issues/11699) + DataType::Varchar + | DataType::Interval + | DataType::Date + | DataType::Time + | DataType::Timestamp + | DataType::Timestamptz + | DataType::Jsonb => AttributeValue::S(scalar_ref.to_text_with_type(data_type)), + DataType::Boolean => AttributeValue::Bool(scalar_ref.into_bool()), + DataType::Bytea => AttributeValue::B(Blob::new(scalar_ref.into_bytea())), + DataType::List(datatype) => { + let list_attr = scalar_ref + .into_list() + .iter() + .map(|x| map_data_type(x, datatype)) + .collect::>>()?; + AttributeValue::L(list_attr) + } + DataType::Struct(st) => { + let mut map = HashMap::with_capacity(st.len()); + for (sub_datum_ref, sub_field) in + scalar_ref.into_struct().iter_fields_ref().zip_eq_debug( + st.iter() + .map(|(name, dt)| Field::with_name(dt.clone(), name)), + ) + { + let attr = map_data_type(sub_datum_ref, &sub_field.data_type())?; + map.insert(sub_field.name.clone(), attr); + } + AttributeValue::M(map) + } + }; + Ok(attr) +} diff --git a/src/connector/src/sink/elasticsearch.rs b/src/connector/src/sink/elasticsearch.rs index 578c768f1ce30..3d51e48201c94 100644 --- a/src/connector/src/sink/elasticsearch.rs +++ b/src/connector/src/sink/elasticsearch.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use anyhow::anyhow; use risingwave_common::array::{ @@ -24,7 +24,7 @@ use risingwave_common::types::{JsonbVal, Scalar, ToText}; use serde_json::Value; use super::encoder::{JsonEncoder, RowEncoder}; -use super::remote::ElasticSearchSink; +use super::remote::{ElasticSearchSink, OpensearchSink}; use crate::sink::{Result, Sink}; pub const ES_OPTION_DELIMITER: &str = "delimiter"; pub const ES_OPTION_INDEX_COLUMN: &str = "index_column"; @@ -38,9 +38,9 @@ impl StreamChunkConverter { sink_name: &str, schema: Schema, pk_indices: &Vec, - properties: &HashMap, + properties: &BTreeMap, ) -> Result { - if sink_name == ElasticSearchSink::SINK_NAME { + if is_es_sink(sink_name) { let index_column = properties .get(ES_OPTION_INDEX_COLUMN) .cloned() @@ -170,3 +170,7 @@ impl EsStreamChunkConverter { (self.fn_build_id)(row) } } + +pub fn is_es_sink(sink_name: &str) -> bool { + sink_name == ElasticSearchSink::SINK_NAME || sink_name == OpensearchSink::SINK_NAME +} diff --git a/src/connector/src/sink/encoder/avro.rs b/src/connector/src/sink/encoder/avro.rs index 924beb281eda7..8122126727298 100644 --- a/src/connector/src/sink/encoder/avro.rs +++ b/src/connector/src/sink/encoder/avro.rs @@ -385,6 +385,10 @@ fn encode_field( AvroSchema::Long => maybe.on_base(|s| Ok(Value::Long(s.into_int64())))?, _ => return no_match_err(), }, + DataType::Serial => match inner { + AvroSchema::Long => maybe.on_base(|s| Ok(Value::Long(s.into_serial().into_inner())))?, + _ => return no_match_err(), + }, DataType::Struct(st) => match inner { AvroSchema::Record { .. } => maybe.on_struct(st, inner)?, _ => return no_match_err(), @@ -447,7 +451,7 @@ fn encode_field( DataType::Decimal => return no_match_err(), DataType::Jsonb => return no_match_err(), // Group D: unsupported - DataType::Serial | DataType::Int256 => { + DataType::Int256 => { return no_match_err(); } }; @@ -526,7 +530,14 @@ mod tests { test_ok( &DataType::Int64, - Some(ScalarImpl::Int64(std::i64::MAX)), + Some(ScalarImpl::Int64(i64::MAX)), + r#""long""#, + Value::Long(i64::MAX), + ); + + test_ok( + &DataType::Serial, + Some(ScalarImpl::Serial(i64::MAX.into())), r#""long""#, Value::Long(i64::MAX), ); diff --git a/src/connector/src/sink/encoder/bson.rs b/src/connector/src/sink/encoder/bson.rs new file mode 100644 index 0000000000000..c401d0575a12b --- /dev/null +++ b/src/connector/src/sink/encoder/bson.rs @@ -0,0 +1,203 @@ +// Copyright 2024 RisingWave Labs +// +// 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::LazyLock; + +use anyhow::anyhow; +use mongodb::bson::spec::BinarySubtype; +use mongodb::bson::{Binary, Bson, DateTime, Document}; +use risingwave_common::array::RowRef; +use risingwave_common::catalog::{Field, Schema}; +use risingwave_common::log::LogSuppresser; +use risingwave_common::row::Row; +use risingwave_common::types::{DataType, DatumRef, JsonbVal, ScalarRefImpl}; +use risingwave_common::util::iter_util::ZipEqDebug; +use thiserror_ext::AsReport; + +use super::{Result as SinkResult, RowEncoder, SerTo}; +use crate::sink::SinkError; + +static LOG_SUPPERSSER: LazyLock = LazyLock::new(LogSuppresser::default); + +pub struct BsonEncoder { + schema: Schema, + col_indices: Option>, + pk_indices: Vec, +} + +impl BsonEncoder { + pub fn new(schema: Schema, col_indices: Option>, pk_indices: Vec) -> Self { + Self { + schema, + col_indices, + pk_indices, + } + } + + pub fn construct_pk(&self, row: RowRef<'_>) -> Bson { + if self.pk_indices.len() == 1 { + let pk_field = &self.schema.fields[self.pk_indices[0]]; + let pk_datum = row.datum_at(self.pk_indices[0]); + datum_to_bson(pk_field, pk_datum) + } else { + self.pk_indices + .iter() + .map(|&idx| { + let pk_field = &self.schema.fields[idx]; + ( + pk_field.name.clone(), + datum_to_bson(pk_field, row.datum_at(idx)), + ) + }) + .collect::() + .into() + } + } +} + +impl SerTo> for Document { + fn ser_to(self) -> SinkResult> { + mongodb::bson::to_vec(&self).map_err(|err| { + SinkError::Mongodb(anyhow!(err).context("cannot serialize Document to Vec")) + }) + } +} + +impl RowEncoder for BsonEncoder { + type Output = Document; + + fn encode_cols( + &self, + row: impl Row, + col_indices: impl Iterator, + ) -> SinkResult { + Ok(col_indices + .map(|idx| (&self.schema.fields[idx], row.datum_at(idx))) + .map(|(field, datum)| (field.name.clone(), datum_to_bson(field, datum))) + .collect()) + } + + fn schema(&self) -> &Schema { + &self.schema + } + + fn col_indices(&self) -> Option<&[usize]> { + self.col_indices.as_ref().map(Vec::as_ref) + } +} + +/// We support converting all types to `MongoDB`. If there is an unmatched type, it will be +/// converted to its string representation. If there is a conversion error, a warning log is printed +/// and a `Bson::Null` is returned +fn datum_to_bson(field: &Field, datum: DatumRef<'_>) -> Bson { + let scalar_ref = match datum { + None => { + return Bson::Null; + } + Some(datum) => datum, + }; + + let data_type = field.data_type(); + + match (data_type, scalar_ref) { + (DataType::Int16, ScalarRefImpl::Int16(v)) => Bson::Int32(v as i32), + (DataType::Int32, ScalarRefImpl::Int32(v)) => Bson::Int32(v), + (DataType::Int64, ScalarRefImpl::Int64(v)) => Bson::Int64(v), + (DataType::Int256, ScalarRefImpl::Int256(v)) => Bson::String(v.to_string()), + (DataType::Float32, ScalarRefImpl::Float32(v)) => Bson::Double(v.into_inner() as f64), + (DataType::Float64, ScalarRefImpl::Float64(v)) => Bson::Double(v.into_inner()), + (DataType::Varchar, ScalarRefImpl::Utf8(v)) => Bson::String(v.to_string()), + (DataType::Boolean, ScalarRefImpl::Bool(v)) => Bson::Boolean(v), + (DataType::Decimal, ScalarRefImpl::Decimal(v)) => { + let decimal_str = v.to_string(); + let converted = decimal_str.parse(); + match converted { + Ok(v) => Bson::Decimal128(v), + Err(err) => { + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + suppressed_count, + error = %err.as_report(), + ?field, + "risingwave decimal {} convert to bson decimal128 failed", + decimal_str, + ); + } + Bson::Null + } + } + } + (DataType::Interval, ScalarRefImpl::Interval(v)) => Bson::String(v.to_string()), + (DataType::Date, ScalarRefImpl::Date(v)) => Bson::String(v.to_string()), + (DataType::Time, ScalarRefImpl::Time(v)) => Bson::String(v.to_string()), + (DataType::Timestamp, ScalarRefImpl::Timestamp(v)) => { + Bson::DateTime(DateTime::from_millis(v.0.and_utc().timestamp_millis())) + } + (DataType::Timestamptz, ScalarRefImpl::Timestamptz(v)) => { + Bson::DateTime(DateTime::from_millis(v.timestamp_millis())) + } + (DataType::Jsonb, ScalarRefImpl::Jsonb(v)) => { + let jsonb_val: JsonbVal = v.into(); + match jsonb_val.take().try_into() { + Ok(doc) => doc, + Err(err) => { + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + suppressed_count, + error = %err.as_report(), + ?field, + "convert jsonb to mongodb bson failed", + ); + } + Bson::Null + } + } + } + (DataType::Serial, ScalarRefImpl::Serial(v)) => Bson::Int64(v.into_inner()), + (DataType::Struct(st), ScalarRefImpl::Struct(struct_ref)) => { + let mut doc = Document::new(); + for (sub_datum_ref, sub_field) in struct_ref.iter_fields_ref().zip_eq_debug( + st.iter() + .map(|(name, dt)| Field::with_name(dt.clone(), name)), + ) { + doc.insert( + sub_field.name.clone(), + datum_to_bson(&sub_field, sub_datum_ref), + ); + } + Bson::Document(doc) + } + (DataType::List(dt), ScalarRefImpl::List(v)) => { + let inner_field = Field::unnamed(Box::::into_inner(dt)); + v.iter() + .map(|scalar_ref| datum_to_bson(&inner_field, scalar_ref)) + .collect::() + } + (DataType::Bytea, ScalarRefImpl::Bytea(v)) => Bson::Binary(Binary { + subtype: BinarySubtype::Generic, + bytes: v.into(), + }), + _ => { + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + suppressed_count, + ?field, + ?scalar_ref, + "datum_to_bson: unsupported data type" + ); + } + Bson::Null + } + } +} diff --git a/src/connector/src/sink/encoder/json.rs b/src/connector/src/sink/encoder/json.rs index bbd424d5db036..4748116609700 100644 --- a/src/connector/src/sink/encoder/json.rs +++ b/src/connector/src/sink/encoder/json.rs @@ -110,19 +110,6 @@ impl JsonEncoder { } } - pub fn new_with_bigquery(schema: Schema, col_indices: Option>) -> Self { - Self { - schema, - col_indices, - time_handling_mode: TimeHandlingMode::Milli, - date_handling_mode: DateHandlingMode::String, - timestamp_handling_mode: TimestampHandlingMode::String, - timestamptz_handling_mode: TimestamptzHandlingMode::UtcString, - custom_json_type: CustomJsonType::BigQuery, - kafka_connect: None, - } - } - pub fn with_kafka_connect(self, kafka_connect: KafkaConnectParams) -> Self { Self { kafka_connect: Some(Arc::new(kafka_connect)), @@ -200,14 +187,7 @@ fn datum_to_json_object( ) -> ArrayResult { let scalar_ref = match datum { None => { - if let CustomJsonType::BigQuery = custom_json_type - && matches!(field.data_type(), DataType::List(_)) - { - // Bigquery need to convert null of array to empty array https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types - return Ok(Value::Array(vec![])); - } else { - return Ok(Value::Null); - } + return Ok(Value::Null); } Some(datum) => datum, }; @@ -229,6 +209,10 @@ fn datum_to_json_object( (DataType::Int64, ScalarRefImpl::Int64(v)) => { json!(v) } + (DataType::Serial, ScalarRefImpl::Serial(v)) => { + // The serial type needs to be handled as a string to prevent primary key conflicts caused by the precision issues of JSON numbers. + json!(format!("{:#018x}", v.into_inner())) + } (DataType::Float32, ScalarRefImpl::Float32(v)) => { json!(f32::from(v)) } @@ -245,10 +229,7 @@ fn datum_to_json_object( v.rescale(*s as u32); json!(v.to_text()) } - CustomJsonType::Es - | CustomJsonType::None - | CustomJsonType::BigQuery - | CustomJsonType::StarRocks => { + CustomJsonType::Es | CustomJsonType::None | CustomJsonType::StarRocks => { json!(v.to_text()) } }, @@ -288,7 +269,7 @@ fn datum_to_json_object( } }, (DataType::Timestamp, ScalarRefImpl::Timestamp(v)) => match timestamp_handling_mode { - TimestampHandlingMode::Milli => json!(v.0.timestamp_millis()), + TimestampHandlingMode::Milli => json!(v.0.and_utc().timestamp_millis()), TimestampHandlingMode::String => json!(v.0.format("%Y-%m-%d %H:%M:%S%.6f").to_string()), }, (DataType::Bytea, ScalarRefImpl::Bytea(v)) => { @@ -300,7 +281,7 @@ fn datum_to_json_object( } (DataType::Jsonb, ScalarRefImpl::Jsonb(jsonb_ref)) => match custom_json_type { CustomJsonType::Es | CustomJsonType::StarRocks => JsonbVal::from(jsonb_ref).take(), - CustomJsonType::Doris(_) | CustomJsonType::None | CustomJsonType::BigQuery => { + CustomJsonType::Doris(_) | CustomJsonType::None => { json!(jsonb_ref.to_string()) } }, @@ -351,7 +332,7 @@ fn datum_to_json_object( "starrocks can't support struct".to_string(), )); } - CustomJsonType::Es | CustomJsonType::None | CustomJsonType::BigQuery => { + CustomJsonType::Es | CustomJsonType::None => { let mut map = Map::with_capacity(st.len()); for (sub_datum_ref, sub_field) in struct_ref.iter_fields_ref().zip_eq_debug( st.iter() @@ -425,7 +406,7 @@ pub(crate) fn schema_type_mapping(rw_type: &DataType) -> &'static str { DataType::List(_) => "array", DataType::Bytea => "bytes", DataType::Jsonb => "string", - DataType::Serial => "int32", + DataType::Serial => "string", DataType::Int256 => "string", } } @@ -457,13 +438,13 @@ fn type_as_json_schema(rw_type: &DataType) -> Map { #[cfg(test)] mod tests { - use risingwave_common::types::{ - DataType, Date, Decimal, Interval, Scalar, ScalarImpl, StructRef, StructType, StructValue, - Time, Timestamp, + Date, Decimal, Interval, Scalar, ScalarImpl, StructRef, StructType, StructValue, Time, + Timestamp, }; use super::*; + #[test] fn test_to_json_basic_type() { let mock_field = Field { @@ -508,7 +489,7 @@ mod tests { data_type: DataType::Int64, ..mock_field.clone() }, - Some(ScalarImpl::Int64(std::i64::MAX).as_scalar_ref_impl()), + Some(ScalarImpl::Int64(i64::MAX).as_scalar_ref_impl()), DateHandlingMode::FromCe, TimestampHandlingMode::String, TimestamptzHandlingMode::UtcString, @@ -518,7 +499,25 @@ mod tests { .unwrap(); assert_eq!( serde_json::to_string(&int64_value).unwrap(), - std::i64::MAX.to_string() + i64::MAX.to_string() + ); + + let serial_value = datum_to_json_object( + &Field { + data_type: DataType::Serial, + ..mock_field.clone() + }, + Some(ScalarImpl::Serial(i64::MAX.into()).as_scalar_ref_impl()), + DateHandlingMode::FromCe, + TimestampHandlingMode::String, + TimestamptzHandlingMode::UtcString, + TimeHandlingMode::Milli, + &CustomJsonType::None, + ) + .unwrap(); + assert_eq!( + serde_json::to_string(&serial_value).unwrap(), + format!("\"{:#018x}\"", i64::MAX) ); // https://github.com/debezium/debezium/blob/main/debezium-core/src/main/java/io/debezium/time/ZonedTimestamp.java @@ -843,7 +842,7 @@ mod tests { let schema = json_converter_with_schema(json!({}), "test".to_owned(), fields.iter()) ["schema"] .to_string(); - let ans = r#"{"fields":[{"field":"v1","optional":true,"type":"boolean"},{"field":"v2","optional":true,"type":"int16"},{"field":"v3","optional":true,"type":"int32"},{"field":"v4","optional":true,"type":"float"},{"field":"v5","optional":true,"type":"string"},{"field":"v6","optional":true,"type":"int32"},{"field":"v7","optional":true,"type":"string"},{"field":"v8","optional":true,"type":"int64"},{"field":"v9","optional":true,"type":"string"},{"field":"v10","fields":[{"field":"a","optional":true,"type":"int64"},{"field":"b","optional":true,"type":"string"},{"field":"c","fields":[{"field":"aa","optional":true,"type":"int64"},{"field":"bb","optional":true,"type":"double"}],"optional":true,"type":"struct"}],"optional":true,"type":"struct"},{"field":"v11","items":{"items":{"fields":[{"field":"aa","optional":true,"type":"int64"},{"field":"bb","optional":true,"type":"double"}],"optional":true,"type":"struct"},"optional":true,"type":"array"},"optional":true,"type":"array"},{"field":"12","optional":true,"type":"string"},{"field":"13","optional":true,"type":"int32"},{"field":"14","optional":true,"type":"string"}],"name":"test","optional":false,"type":"struct"}"#; + let ans = r#"{"fields":[{"field":"v1","optional":true,"type":"boolean"},{"field":"v2","optional":true,"type":"int16"},{"field":"v3","optional":true,"type":"int32"},{"field":"v4","optional":true,"type":"float"},{"field":"v5","optional":true,"type":"string"},{"field":"v6","optional":true,"type":"int32"},{"field":"v7","optional":true,"type":"string"},{"field":"v8","optional":true,"type":"int64"},{"field":"v9","optional":true,"type":"string"},{"field":"v10","fields":[{"field":"a","optional":true,"type":"int64"},{"field":"b","optional":true,"type":"string"},{"field":"c","fields":[{"field":"aa","optional":true,"type":"int64"},{"field":"bb","optional":true,"type":"double"}],"optional":true,"type":"struct"}],"optional":true,"type":"struct"},{"field":"v11","items":{"items":{"fields":[{"field":"aa","optional":true,"type":"int64"},{"field":"bb","optional":true,"type":"double"}],"optional":true,"type":"struct"},"optional":true,"type":"array"},"optional":true,"type":"array"},{"field":"12","optional":true,"type":"string"},{"field":"13","optional":true,"type":"string"},{"field":"14","optional":true,"type":"string"}],"name":"test","optional":false,"type":"struct"}"#; assert_eq!(schema, ans); } } diff --git a/src/connector/src/sink/encoder/mod.rs b/src/connector/src/sink/encoder/mod.rs index 0b6899dbad955..889d0162784bb 100644 --- a/src/connector/src/sink/encoder/mod.rs +++ b/src/connector/src/sink/encoder/mod.rs @@ -21,11 +21,14 @@ use risingwave_common::row::Row; use crate::sink::Result; mod avro; +mod bson; mod json; mod proto; pub mod template; +pub mod text; pub use avro::{AvroEncoder, AvroHeader}; +pub use bson::BsonEncoder; pub use json::JsonEncoder; pub use proto::{ProtoEncoder, ProtoHeader}; @@ -57,7 +60,7 @@ pub trait RowEncoder { /// * an json object /// * a protobuf message /// * an avro record -/// into +/// into /// * string (required by kinesis key) /// * bytes /// @@ -144,8 +147,6 @@ pub enum CustomJsonType { Es, // starrocks' need jsonb is struct StarRocks, - // bigquery need null array -> [] - BigQuery, None, } diff --git a/src/connector/src/sink/encoder/proto.rs b/src/connector/src/sink/encoder/proto.rs index a5f1090dbafaf..a0e4d41dc58de 100644 --- a/src/connector/src/sink/encoder/proto.rs +++ b/src/connector/src/sink/encoder/proto.rs @@ -77,7 +77,7 @@ impl ProtoEncoder { } pub struct ProtoEncoded { - message: DynamicMessage, + pub message: DynamicMessage, header: ProtoHeader, } @@ -205,6 +205,7 @@ impl MaybeData for () { /// * Top level is always a message. /// * All message fields can be omitted in proto3. /// * All repeated elements must have a value. +/// /// So we handle [`ScalarRefImpl`] rather than [`DatumRef`] here. impl MaybeData for ScalarRefImpl<'_> { type Out = Value; @@ -275,6 +276,7 @@ fn encode_fields<'a>( // Full name of Well-Known Types const WKT_TIMESTAMP: &str = "google.protobuf.Timestamp"; +#[expect(dead_code)] const WKT_BOOL_VALUE: &str = "google.protobuf.BoolValue"; /// Handles both `validate` (without actual data) and `encode`. @@ -307,7 +309,6 @@ fn encode_field( proto_field.kind() ))) }; - let value = match &data_type { // Group A: perfect match between RisingWave types and ProtoBuf types DataType::Boolean => match (expect_list, proto_field.kind()) { @@ -364,18 +365,59 @@ fn encode_field( Ok(Value::Message(message.transcode_to_dynamic())) })? } + (false, Kind::String) => { + maybe.on_base(|s| Ok(Value::String(s.into_timestamptz().to_string())))? + } + _ => return no_match_err(), + }, + DataType::Jsonb => match (expect_list, proto_field.kind()) { + (false, Kind::String) => { + maybe.on_base(|s| Ok(Value::String(s.into_jsonb().to_string())))? + } + _ => return no_match_err(), /* Value, NullValue, Struct (map), ListValue + * Group C: experimental */ + }, + DataType::Int16 => match (expect_list, proto_field.kind()) { + (false, Kind::Int64) => maybe.on_base(|s| Ok(Value::I64(s.into_int16() as i64)))?, _ => return no_match_err(), }, - DataType::Jsonb => return no_match_err(), // Value, NullValue, Struct (map), ListValue - // Group C: experimental - DataType::Int16 => return no_match_err(), - DataType::Date => return no_match_err(), // google.type.Date - DataType::Time => return no_match_err(), // google.type.TimeOfDay - DataType::Timestamp => return no_match_err(), // google.type.DateTime - DataType::Decimal => return no_match_err(), // google.type.Decimal - DataType::Interval => return no_match_err(), - // Group D: unsupported - DataType::Serial | DataType::Int256 => { + DataType::Date => match (expect_list, proto_field.kind()) { + (false, Kind::Int32) => { + maybe.on_base(|s| Ok(Value::I32(s.into_date().get_nums_days_unix_epoch())))? + } + _ => return no_match_err(), // google.type.Date + }, + DataType::Time => match (expect_list, proto_field.kind()) { + (false, Kind::String) => { + maybe.on_base(|s| Ok(Value::String(s.into_time().to_string())))? + } + _ => return no_match_err(), // google.type.TimeOfDay + }, + DataType::Timestamp => match (expect_list, proto_field.kind()) { + (false, Kind::String) => { + maybe.on_base(|s| Ok(Value::String(s.into_timestamp().to_string())))? + } + _ => return no_match_err(), // google.type.DateTime + }, + DataType::Decimal => match (expect_list, proto_field.kind()) { + (false, Kind::String) => { + maybe.on_base(|s| Ok(Value::String(s.into_decimal().to_string())))? + } + _ => return no_match_err(), // google.type.Decimal + }, + DataType::Interval => match (expect_list, proto_field.kind()) { + (false, Kind::String) => { + maybe.on_base(|s| Ok(Value::String(s.into_interval().as_iso_8601())))? + } + _ => return no_match_err(), // Group D: unsupported + }, + DataType::Serial => match (expect_list, proto_field.kind()) { + (false, Kind::Int64) => { + maybe.on_base(|s| Ok(Value::I64(s.into_serial().as_row_id())))? + } + _ => return no_match_err(), // Group D: unsupported + }, + DataType::Int256 => { return no_match_err(); } }; @@ -398,7 +440,6 @@ mod tests { let pool_bytes = std::fs::read(pool_path).unwrap(); let pool = prost_reflect::DescriptorPool::decode(pool_bytes.as_ref()).unwrap(); let descriptor = pool.get_message_by_name("recursive.AllTypes").unwrap(); - let schema = Schema::new(vec![ Field::with_name(DataType::Boolean, "bool_field"), Field::with_name(DataType::Varchar, "string_field"), diff --git a/src/connector/src/sink/encoder/text.rs b/src/connector/src/sink/encoder/text.rs new file mode 100644 index 0000000000000..734ac8bd6a425 --- /dev/null +++ b/src/connector/src/sink/encoder/text.rs @@ -0,0 +1,108 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_common::catalog::Schema; +use risingwave_common::types::{DataType, ToText}; + +use super::RowEncoder; + +pub struct TextEncoder { + pub schema: Schema, + // the column must contain only one element + pub col_index: usize, +} + +impl TextEncoder { + pub fn new(schema: Schema, col_index: usize) -> Self { + Self { schema, col_index } + } +} + +impl RowEncoder for TextEncoder { + type Output = String; + + fn schema(&self) -> &risingwave_common::catalog::Schema { + &self.schema + } + + fn col_indices(&self) -> Option<&[usize]> { + Some(std::slice::from_ref(&self.col_index)) + } + + fn encode_cols( + &self, + row: impl risingwave_common::row::Row, + col_indices: impl Iterator, + ) -> crate::sink::Result { + // It is guaranteed by the caller that col_indices contains only one element + let mut result = String::new(); + for col_index in col_indices { + let datum = row.datum_at(col_index); + let data_type = &self.schema.fields[col_index].data_type; + if data_type == &DataType::Boolean { + result = if let Some(scalar_impl) = datum { + scalar_impl.into_bool().to_string() + } else { + "NULL".to_string() + } + } else { + result = datum.to_text_with_type(data_type); + } + } + + Ok(result) + } +} + +#[cfg(test)] +mod test { + use risingwave_common::catalog::Field; + use risingwave_common::row::OwnedRow; + use risingwave_common::types::ScalarImpl; + + use super::*; + + #[test] + fn test_text_encoder_ser_bool() { + let schema = Schema::new(vec![Field::with_name(DataType::Boolean, "col1")]); + let encoder = TextEncoder::new(schema, 0); + + let row = OwnedRow::new(vec![Some(ScalarImpl::Bool(true))]); + assert_eq!( + encoder + .encode_cols(&row, std::iter::once(0)) + .unwrap() + .as_str(), + "true" + ); + + let row = OwnedRow::new(vec![Some(ScalarImpl::Bool(false))]); + assert_eq!( + encoder + .encode_cols(&row, std::iter::once(0)) + .unwrap() + .as_str(), + "false" + ); + + let row = OwnedRow::new(vec![None]); + assert_eq!( + encoder + .encode_cols(&row, std::iter::once(0)) + .unwrap() + .as_str(), + "NULL" + ); + } +} diff --git a/src/connector/src/sink/formatter/mod.rs b/src/connector/src/sink/formatter/mod.rs index d923d337a3ffb..4628a925da98d 100644 --- a/src/connector/src/sink/formatter/mod.rs +++ b/src/connector/src/sink/formatter/mod.rs @@ -24,10 +24,12 @@ mod upsert; pub use append_only::AppendOnlyFormatter; pub use debezium_json::{DebeziumAdapterOpts, DebeziumJsonFormatter}; use risingwave_common::catalog::Schema; +use risingwave_common::types::DataType; pub use upsert::UpsertFormatter; use super::catalog::{SinkEncode, SinkFormat, SinkFormatDesc}; use super::encoder::template::TemplateEncoder; +use super::encoder::text::TextEncoder; use super::encoder::{ DateHandlingMode, KafkaConnectParams, TimeHandlingMode, TimestamptzHandlingMode, }; @@ -43,9 +45,9 @@ pub trait SinkFormatter { type V; /// * Key may be None so that messages are partitioned using round-robin. - /// For example append-only without `primary_key` (aka `downstream_pk`) set. + /// For example append-only without `primary_key` (aka `downstream_pk`) set. /// * Value may be None so that messages with same key are removed during log compaction. - /// For example debezium tombstone event. + /// For example debezium tombstone event. fn format_chunk( &self, chunk: &StreamChunk, @@ -68,13 +70,226 @@ macro_rules! tri { } pub enum SinkFormatterImpl { + // append-only AppendOnlyJson(AppendOnlyFormatter), + AppendOnlyTextJson(AppendOnlyFormatter), + AppendOnlyAvro(AppendOnlyFormatter), + AppendOnlyTextAvro(AppendOnlyFormatter), AppendOnlyProto(AppendOnlyFormatter), + AppendOnlyTextProto(AppendOnlyFormatter), + AppendOnlyTemplate(AppendOnlyFormatter), + AppendOnlyTextTemplate(AppendOnlyFormatter), + // upsert UpsertJson(UpsertFormatter), + UpsertTextJson(UpsertFormatter), UpsertAvro(UpsertFormatter), - DebeziumJson(DebeziumJsonFormatter), - AppendOnlyTemplate(AppendOnlyFormatter), + UpsertTextAvro(UpsertFormatter), UpsertTemplate(UpsertFormatter), + UpsertTextTemplate(UpsertFormatter), + // debezium + DebeziumJson(DebeziumJsonFormatter), +} + +#[derive(Debug, Clone)] +pub struct EncoderParams<'a> { + format_desc: &'a SinkFormatDesc, + schema: Schema, + db_name: String, + sink_from_name: String, + topic: &'a str, +} + +/// Each encoder shall be able to be built from parameters. +/// +/// This is not part of `RowEncoder` trait, because that one is about how an encoder completes its +/// own job as a self-contained unit, with a custom `new` asking for only necessary info; while this +/// one is about how different encoders can be selected from a common SQL interface. +pub trait EncoderBuild: Sized { + /// Pass `pk_indices: None` for value/payload and `Some` for key. Certain encoder builds + /// differently when used as key vs value. + async fn build(params: EncoderParams<'_>, pk_indices: Option>) -> Result; +} + +impl EncoderBuild for JsonEncoder { + async fn build(b: EncoderParams<'_>, pk_indices: Option>) -> Result { + let timestamptz_mode = TimestamptzHandlingMode::from_options(&b.format_desc.options)?; + let encoder = JsonEncoder::new( + b.schema, + pk_indices, + DateHandlingMode::FromCe, + TimestampHandlingMode::Milli, + timestamptz_mode, + TimeHandlingMode::Milli, + ); + let encoder = if let Some(s) = b.format_desc.options.get("schemas.enable") { + match s.to_lowercase().parse::() { + Ok(true) => { + let kafka_connect = KafkaConnectParams { + schema_name: format!("{}.{}", b.db_name, b.sink_from_name), + }; + encoder.with_kafka_connect(kafka_connect) + } + Ok(false) => encoder, + _ => { + return Err(SinkError::Config(anyhow!( + "schemas.enable is expected to be `true` or `false`, got {s}", + ))); + } + } + } else { + encoder + }; + Ok(encoder) + } +} + +impl EncoderBuild for ProtoEncoder { + async fn build(b: EncoderParams<'_>, pk_indices: Option>) -> Result { + // TODO: better to be a compile-time assert + assert!(pk_indices.is_none()); + // By passing `None` as `aws_auth_props`, reading from `s3://` not supported yet. + let (descriptor, sid) = + crate::schema::protobuf::fetch_descriptor(&b.format_desc.options, b.topic, None) + .await + .map_err(|e| SinkError::Config(anyhow!(e)))?; + let header = match sid { + None => ProtoHeader::None, + Some(sid) => ProtoHeader::ConfluentSchemaRegistry(sid), + }; + ProtoEncoder::new(b.schema, None, descriptor, header) + } +} + +impl EncoderBuild for TextEncoder { + async fn build(params: EncoderParams<'_>, pk_indices: Option>) -> Result { + let Some(pk_indices) = pk_indices else { + return Err(SinkError::Config(anyhow!( + "TextEncoder requires primary key columns to be specified" + ))); + }; + if pk_indices.len() != 1 { + return Err(SinkError::Config(anyhow!( + "The key encode is TEXT, but the primary key has {} columns. The key encode TEXT requires the primary key to be a single column", + pk_indices.len() + ))); + } + + let schema_ref = params.schema.fields().get(pk_indices[0]).ok_or_else(|| { + SinkError::Config(anyhow!( + "The primary key column index {} is out of bounds in schema {:?}", + pk_indices[0], + params.schema + )) + })?; + match &schema_ref.data_type() { + DataType::Varchar + | DataType::Boolean + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Int256 + | DataType::Serial => {} + _ => { + // why we don't allow float as text for key encode: https://github.com/risingwavelabs/risingwave/pull/16377#discussion_r1591864960 + return Err(SinkError::Config( + anyhow!( + "The key encode is TEXT, but the primary key column {} has type {}. The key encode TEXT requires the primary key column to be of type varchar, bool, small int, int, big int, serial or rw_int256.", + schema_ref.name, + schema_ref.data_type + ), + )); + } + } + + Ok(Self::new(params.schema, pk_indices[0])) + } +} + +impl EncoderBuild for AvroEncoder { + async fn build(b: EncoderParams<'_>, pk_indices: Option>) -> Result { + let loader = + crate::schema::SchemaLoader::from_format_options(b.topic, &b.format_desc.options) + .map_err(|e| SinkError::Config(anyhow!(e)))?; + + let (schema_id, avro) = match pk_indices { + Some(_) => loader + .load_key_schema() + .await + .map_err(|e| SinkError::Config(anyhow!(e)))?, + None => loader + .load_val_schema() + .await + .map_err(|e| SinkError::Config(anyhow!(e)))?, + }; + AvroEncoder::new( + b.schema, + pk_indices, + std::sync::Arc::new(avro), + AvroHeader::ConfluentSchemaRegistry(schema_id), + ) + } +} + +impl EncoderBuild for TemplateEncoder { + async fn build(b: EncoderParams<'_>, pk_indices: Option>) -> Result { + let option_name = match pk_indices { + Some(_) => KEY_FORMAT, + None => VALUE_FORMAT, + }; + let template = b.format_desc.options.get(option_name).ok_or_else(|| { + SinkError::Config(anyhow!( + "Cannot find '{option_name}',please set it or use JSON" + )) + })?; + Ok(TemplateEncoder::new(b.schema, pk_indices, template.clone())) + } +} + +struct FormatterParams<'a> { + builder: EncoderParams<'a>, + pk_indices: Vec, +} + +/// Each formatter shall be able to be built from parameters. +/// +/// This is not part of `SinkFormatter` trait, because that is about how a formatter completes its +/// own job as a self-contained unit, with a custom `new` asking for only necessary info; while this +/// one is about how different formatters can be selected from a common SQL interface. +trait FormatterBuild: Sized { + async fn build(b: FormatterParams<'_>) -> Result; +} + +impl FormatterBuild for AppendOnlyFormatter { + async fn build(b: FormatterParams<'_>) -> Result { + let key_encoder = match b.pk_indices.is_empty() { + true => None, + false => Some(KE::build(b.builder.clone(), Some(b.pk_indices)).await?), + }; + let val_encoder = VE::build(b.builder, None).await?; + Ok(AppendOnlyFormatter::new(key_encoder, val_encoder)) + } +} + +impl FormatterBuild for UpsertFormatter { + async fn build(b: FormatterParams<'_>) -> Result { + let key_encoder = KE::build(b.builder.clone(), Some(b.pk_indices)).await?; + let val_encoder = VE::build(b.builder, None).await?; + Ok(UpsertFormatter::new(key_encoder, val_encoder)) + } +} + +impl FormatterBuild for DebeziumJsonFormatter { + async fn build(b: FormatterParams<'_>) -> Result { + assert_eq!(b.builder.format_desc.encode, SinkEncode::Json); + + Ok(DebeziumJsonFormatter::new( + b.builder.schema, + b.pk_indices, + b.builder.db_name, + b.builder.sink_from_name, + DebeziumAdapterOpts::default(), + )) + } } impl SinkFormatterImpl { @@ -86,187 +301,83 @@ impl SinkFormatterImpl { sink_from_name: String, topic: &str, ) -> Result { - let err_unsupported = || { - Err(SinkError::Config(anyhow!( - "sink format/encode unsupported: {:?} {:?}", - format_desc.format, - format_desc.encode, - ))) + use {SinkEncode as E, SinkFormat as F, SinkFormatterImpl as Impl}; + let p = FormatterParams { + builder: EncoderParams { + format_desc, + schema, + db_name, + sink_from_name, + topic, + }, + pk_indices, }; - let timestamptz_mode = TimestamptzHandlingMode::from_options(&format_desc.options)?; - - match format_desc.format { - SinkFormat::AppendOnly => { - let key_encoder = (!pk_indices.is_empty()).then(|| { - JsonEncoder::new( - schema.clone(), - Some(pk_indices.clone()), - DateHandlingMode::FromCe, - TimestampHandlingMode::Milli, - timestamptz_mode, - TimeHandlingMode::Milli, - ) - }); - - match format_desc.encode { - SinkEncode::Json => { - let val_encoder = JsonEncoder::new( - schema, - None, - DateHandlingMode::FromCe, - TimestampHandlingMode::Milli, - timestamptz_mode, - TimeHandlingMode::Milli, - ); - let formatter = AppendOnlyFormatter::new(key_encoder, val_encoder); - Ok(SinkFormatterImpl::AppendOnlyJson(formatter)) - } - SinkEncode::Protobuf => { - // By passing `None` as `aws_auth_props`, reading from `s3://` not supported yet. - let (descriptor, sid) = crate::schema::protobuf::fetch_descriptor( - &format_desc.options, - topic, - None, - ) - .await - .map_err(|e| SinkError::Config(anyhow!(e)))?; - let header = match sid { - None => ProtoHeader::None, - Some(sid) => ProtoHeader::ConfluentSchemaRegistry(sid), - }; - let val_encoder = ProtoEncoder::new(schema, None, descriptor, header)?; - let formatter = AppendOnlyFormatter::new(key_encoder, val_encoder); - Ok(SinkFormatterImpl::AppendOnlyProto(formatter)) - } - SinkEncode::Avro => err_unsupported(), - SinkEncode::Template => { - let key_format = format_desc.options.get(KEY_FORMAT).ok_or_else(|| { - SinkError::Config(anyhow!( - "Cannot find 'key_format',please set it or use JSON" - )) - })?; - let value_format = - format_desc.options.get(VALUE_FORMAT).ok_or_else(|| { - SinkError::Config(anyhow!( - "Cannot find 'redis_value_format',please set it or use JSON" - )) - })?; - let key_encoder = TemplateEncoder::new( - schema.clone(), - Some(pk_indices), - key_format.clone(), - ); - let val_encoder = TemplateEncoder::new(schema, None, value_format.clone()); - Ok(SinkFormatterImpl::AppendOnlyTemplate( - AppendOnlyFormatter::new(Some(key_encoder), val_encoder), - )) - } + + // When defining `SinkFormatterImpl` we already linked each variant (eg `AppendOnlyJson`) to + // an instantiation (eg `AppendOnlyFormatter`) that implements the + // trait `FormatterBuild`. + // + // Here we just need to match a `(format, encode)` to a variant, and rustc shall be able to + // find the corresponding instantiation. + + // However, + // `Impl::AppendOnlyJson(FormatterBuild::build(p).await?)` + // fails to be inferred without the following dummy wrapper. + async fn build(p: FormatterParams<'_>) -> Result { + T::build(p).await + } + + Ok( + match ( + &format_desc.format, + &format_desc.encode, + &format_desc.key_encode, + ) { + (F::AppendOnly, E::Json, Some(E::Text)) => { + Impl::AppendOnlyTextJson(build(p).await?) } - } - SinkFormat::Debezium => { - if format_desc.encode != SinkEncode::Json { - return err_unsupported(); + (F::AppendOnly, E::Json, None) => Impl::AppendOnlyJson(build(p).await?), + (F::AppendOnly, E::Avro, Some(E::Text)) => { + Impl::AppendOnlyTextAvro(build(p).await?) } - - Ok(SinkFormatterImpl::DebeziumJson(DebeziumJsonFormatter::new( - schema, - pk_indices, - db_name, - sink_from_name, - DebeziumAdapterOpts::default(), - ))) - } - SinkFormat::Upsert => { - match format_desc.encode { - SinkEncode::Json => { - let mut key_encoder = JsonEncoder::new( - schema.clone(), - Some(pk_indices), - DateHandlingMode::FromCe, - TimestampHandlingMode::Milli, - timestamptz_mode, - TimeHandlingMode::Milli, - ); - let mut val_encoder = JsonEncoder::new( - schema, - None, - DateHandlingMode::FromCe, - TimestampHandlingMode::Milli, - timestamptz_mode, - TimeHandlingMode::Milli, - ); - - if let Some(s) = format_desc.options.get("schemas.enable") { - match s.to_lowercase().parse::() { - Ok(true) => { - let kafka_connect = KafkaConnectParams { - schema_name: format!("{}.{}", db_name, sink_from_name), - }; - key_encoder = - key_encoder.with_kafka_connect(kafka_connect.clone()); - val_encoder = val_encoder.with_kafka_connect(kafka_connect); - } - Ok(false) => {} - _ => { - return Err(SinkError::Config(anyhow!( - "schemas.enable is expected to be `true` or `false`, got {}", - s - ))); - } - } - }; - - // Initialize the upsert_stream - let formatter = UpsertFormatter::new(key_encoder, val_encoder); - Ok(SinkFormatterImpl::UpsertJson(formatter)) - } - SinkEncode::Template => { - let key_format = format_desc.options.get(KEY_FORMAT).ok_or_else(|| { - SinkError::Config(anyhow!( - "Cannot find 'key_format',please set it or use JSON" - )) - })?; - let value_format = - format_desc.options.get(VALUE_FORMAT).ok_or_else(|| { - SinkError::Config(anyhow!( - "Cannot find 'redis_value_format',please set it or use JSON" - )) - })?; - let key_encoder = TemplateEncoder::new( - schema.clone(), - Some(pk_indices), - key_format.clone(), - ); - let val_encoder = TemplateEncoder::new(schema, None, value_format.clone()); - Ok(SinkFormatterImpl::UpsertTemplate(UpsertFormatter::new( - key_encoder, - val_encoder, - ))) - } - SinkEncode::Avro => { - let (key_schema, val_schema) = - crate::schema::avro::fetch_schema(&format_desc.options, topic) - .await - .map_err(|e| SinkError::Config(anyhow!(e)))?; - let key_encoder = AvroEncoder::new( - schema.clone(), - Some(pk_indices), - key_schema.schema, - AvroHeader::ConfluentSchemaRegistry(key_schema.id), - )?; - let val_encoder = AvroEncoder::new( - schema.clone(), - None, - val_schema.schema, - AvroHeader::ConfluentSchemaRegistry(val_schema.id), - )?; - let formatter = UpsertFormatter::new(key_encoder, val_encoder); - Ok(SinkFormatterImpl::UpsertAvro(formatter)) - } - SinkEncode::Protobuf => err_unsupported(), + (F::AppendOnly, E::Avro, None) => Impl::AppendOnlyAvro(build(p).await?), + (F::AppendOnly, E::Protobuf, Some(E::Text)) => { + Impl::AppendOnlyTextProto(build(p).await?) } - } - } + (F::AppendOnly, E::Protobuf, None) => Impl::AppendOnlyProto(build(p).await?), + (F::AppendOnly, E::Template, Some(E::Text)) => { + Impl::AppendOnlyTextTemplate(build(p).await?) + } + (F::AppendOnly, E::Template, None) => Impl::AppendOnlyTemplate(build(p).await?), + (F::Upsert, E::Json, Some(E::Text)) => Impl::UpsertTextJson(build(p).await?), + (F::Upsert, E::Json, None) => Impl::UpsertJson(build(p).await?), + (F::Upsert, E::Avro, Some(E::Text)) => Impl::UpsertTextAvro(build(p).await?), + (F::Upsert, E::Avro, None) => Impl::UpsertAvro(build(p).await?), + (F::Upsert, E::Template, Some(E::Text)) => { + Impl::UpsertTextTemplate(build(p).await?) + } + (F::Upsert, E::Template, None) => Impl::UpsertTemplate(build(p).await?), + (F::Debezium, E::Json, None) => Impl::DebeziumJson(build(p).await?), + (F::AppendOnly | F::Upsert, E::Text, _) => { + return Err(SinkError::Config(anyhow!( + "ENCODE TEXT is only valid as key encode." + ))); + } + (F::AppendOnly, E::Avro, _) + | (F::Upsert, E::Protobuf, _) + | (F::Debezium, E::Json, Some(_)) + | (F::Debezium, E::Avro | E::Protobuf | E::Template | E::Text, _) + | (F::AppendOnly | F::Upsert, _, Some(E::Template) | Some(E::Json) | Some(E::Avro) | Some(E::Protobuf)) // reject other encode as key encode + => { + return Err(SinkError::Config(anyhow!( + "sink format/encode/key_encode unsupported: {:?} {:?} {:?}", + format_desc.format, + format_desc.encode, + format_desc.key_encode + ))); + } + }, + ) } } @@ -275,11 +386,20 @@ macro_rules! dispatch_sink_formatter_impl { ($impl:expr, $name:ident, $body:expr) => { match $impl { SinkFormatterImpl::AppendOnlyJson($name) => $body, + SinkFormatterImpl::AppendOnlyTextJson($name) => $body, + SinkFormatterImpl::AppendOnlyAvro($name) => $body, + SinkFormatterImpl::AppendOnlyTextAvro($name) => $body, SinkFormatterImpl::AppendOnlyProto($name) => $body, + SinkFormatterImpl::AppendOnlyTextProto($name) => $body, + SinkFormatterImpl::UpsertJson($name) => $body, + SinkFormatterImpl::UpsertTextJson($name) => $body, SinkFormatterImpl::UpsertAvro($name) => $body, + SinkFormatterImpl::UpsertTextAvro($name) => $body, SinkFormatterImpl::DebeziumJson($name) => $body, + SinkFormatterImpl::AppendOnlyTextTemplate($name) => $body, SinkFormatterImpl::AppendOnlyTemplate($name) => $body, + SinkFormatterImpl::UpsertTextTemplate($name) => $body, SinkFormatterImpl::UpsertTemplate($name) => $body, } }; @@ -290,11 +410,20 @@ macro_rules! dispatch_sink_formatter_str_key_impl { ($impl:expr, $name:ident, $body:expr) => { match $impl { SinkFormatterImpl::AppendOnlyJson($name) => $body, + SinkFormatterImpl::AppendOnlyTextJson($name) => $body, + SinkFormatterImpl::AppendOnlyAvro(_) => unreachable!(), + SinkFormatterImpl::AppendOnlyTextAvro($name) => $body, SinkFormatterImpl::AppendOnlyProto($name) => $body, + SinkFormatterImpl::AppendOnlyTextProto($name) => $body, + SinkFormatterImpl::UpsertJson($name) => $body, + SinkFormatterImpl::UpsertTextJson($name) => $body, SinkFormatterImpl::UpsertAvro(_) => unreachable!(), + SinkFormatterImpl::UpsertTextAvro($name) => $body, SinkFormatterImpl::DebeziumJson($name) => $body, + SinkFormatterImpl::AppendOnlyTextTemplate($name) => $body, SinkFormatterImpl::AppendOnlyTemplate($name) => $body, + SinkFormatterImpl::UpsertTextTemplate($name) => $body, SinkFormatterImpl::UpsertTemplate($name) => $body, } }; diff --git a/src/connector/src/sink/google_pubsub.rs b/src/connector/src/sink/google_pubsub.rs new file mode 100644 index 0000000000000..a01daa59c1272 --- /dev/null +++ b/src/connector/src/sink/google_pubsub.rs @@ -0,0 +1,322 @@ +// Copyright 2024 RisingWave Labs +// +// 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::BTreeMap; + +use anyhow::{anyhow, Context}; +use futures::future::try_join_all; +use futures::prelude::future::FutureExt; +use futures::prelude::TryFuture; +use futures::TryFutureExt; +use google_cloud_gax::conn::Environment; +use google_cloud_googleapis::pubsub::v1::PubsubMessage; +use google_cloud_pubsub::apiv1; +use google_cloud_pubsub::client::google_cloud_auth::credentials::CredentialsFile; +use google_cloud_pubsub::client::google_cloud_auth::project; +use google_cloud_pubsub::client::google_cloud_auth::token::DefaultTokenSourceProvider; +use google_cloud_pubsub::client::{Client, ClientConfig}; +use google_cloud_pubsub::publisher::{Awaiter, Publisher}; +use risingwave_common::array::StreamChunk; +use risingwave_common::catalog::Schema; +use risingwave_common::session_config::sink_decouple::SinkDecouple; +use serde_derive::Deserialize; +use serde_with::serde_as; +use tonic::Status; +use with_options::WithOptions; + +use super::catalog::desc::SinkDesc; +use super::catalog::SinkFormatDesc; +use super::formatter::SinkFormatterImpl; +use super::log_store::DeliveryFutureManagerAddFuture; +use super::writer::{ + AsyncTruncateLogSinkerOf, AsyncTruncateSinkWriter, AsyncTruncateSinkWriterExt, FormattedSink, +}; +use super::{DummySinkCommitCoordinator, Result, Sink, SinkError, SinkParam, SinkWriterParam}; +use crate::dispatch_sink_formatter_str_key_impl; + +pub const PUBSUB_SINK: &str = "google_pubsub"; +const PUBSUB_SEND_FUTURE_BUFFER_MAX_SIZE: usize = 65536; + +fn may_delivery_future(awaiter: Vec) -> GooglePubSubSinkDeliveryFuture { + try_join_all(awaiter.into_iter().map(|awaiter| { + awaiter.get().map(|result| { + result + .context("Google Pub/Sub sink error") + .map_err(SinkError::GooglePubSub) + .map(|_| ()) + }) + })) + .map_ok(|_: Vec<()>| ()) + .boxed() +} + +#[serde_as] +#[derive(Clone, Debug, Deserialize, WithOptions)] +pub struct GooglePubSubConfig { + /// The Google Pub/Sub Project ID + #[serde(rename = "pubsub.project_id")] + pub project_id: String, + + /// Specifies the Pub/Sub topic to publish messages + #[serde(rename = "pubsub.topic")] + pub topic: String, + + /// The Google Pub/Sub endpoint URL + #[serde(rename = "pubsub.endpoint")] + pub endpoint: String, + + /// use the connector with a pubsub emulator + /// + #[serde(rename = "pubsub.emulator_host")] + pub emulator_host: Option, + + /// A JSON string containing the service account credentials for authorization, + /// see the [service-account](https://developers.google.com/workspace/guides/create-credentials#create_credentials_for_a_service_account) credentials guide. + /// The provided account credential must have the + /// `pubsub.publisher` [role](https://cloud.google.com/pubsub/docs/access-control#roles) + #[serde(rename = "pubsub.credentials")] + pub credentials: Option, +} + +impl GooglePubSubConfig { + fn from_btreemap(values: BTreeMap) -> Result { + serde_json::from_value::(serde_json::to_value(values).unwrap()) + .map_err(|e| SinkError::Config(anyhow!(e))) + } +} + +#[derive(Clone, Debug)] +pub struct GooglePubSubSink { + pub config: GooglePubSubConfig, + is_append_only: bool, + + schema: Schema, + pk_indices: Vec, + format_desc: SinkFormatDesc, + db_name: String, + sink_from_name: String, +} + +impl Sink for GooglePubSubSink { + type Coordinator = DummySinkCommitCoordinator; + type LogSinker = AsyncTruncateLogSinkerOf; + + const SINK_NAME: &'static str = PUBSUB_SINK; + + fn is_sink_decouple(_desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + match user_specified { + SinkDecouple::Default | SinkDecouple::Enable => Ok(true), + SinkDecouple::Disable => Ok(false), + } + } + + async fn validate(&self) -> Result<()> { + if !self.is_append_only { + return Err(SinkError::GooglePubSub(anyhow!( + "Google Pub/Sub sink only support append-only mode" + ))); + } + + let conf = &self.config; + if matches!((&conf.emulator_host, &conf.credentials), (None, None)) { + return Err(SinkError::GooglePubSub(anyhow!( + "Configure at least one of `pubsub.emulator_host` and `pubsub.credentials` in the Google Pub/Sub sink" + ))); + } + + Ok(()) + } + + async fn new_log_sinker(&self, _writer_param: SinkWriterParam) -> Result { + Ok(GooglePubSubSinkWriter::new( + self.config.clone(), + self.schema.clone(), + self.pk_indices.clone(), + &self.format_desc, + self.db_name.clone(), + self.sink_from_name.clone(), + ) + .await? + .into_log_sinker(PUBSUB_SEND_FUTURE_BUFFER_MAX_SIZE)) + } +} + +impl TryFrom for GooglePubSubSink { + type Error = SinkError; + + fn try_from(param: SinkParam) -> std::result::Result { + let schema = param.schema(); + let config = GooglePubSubConfig::from_btreemap(param.properties)?; + + let format_desc = param + .format_desc + .ok_or_else(|| SinkError::Config(anyhow!("missing FORMAT ... ENCODE ...")))?; + Ok(Self { + config, + is_append_only: param.sink_type.is_append_only(), + + schema, + pk_indices: param.downstream_pk, + format_desc, + db_name: param.db_name, + sink_from_name: param.sink_from_name, + }) + } +} + +struct GooglePubSubPayloadWriter<'w> { + publisher: &'w mut Publisher, + message_vec: Vec, + add_future: DeliveryFutureManagerAddFuture<'w, GooglePubSubSinkDeliveryFuture>, +} + +pub type GooglePubSubSinkDeliveryFuture = + impl TryFuture + Unpin + 'static; + +impl GooglePubSubSinkWriter { + pub async fn new( + config: GooglePubSubConfig, + schema: Schema, + pk_indices: Vec, + format_desc: &SinkFormatDesc, + db_name: String, + sink_from_name: String, + ) -> Result { + let environment = if let Some(ref cred) = config.credentials { + let auth_config = project::Config { + audience: Some(apiv1::conn_pool::AUDIENCE), + scopes: Some(&apiv1::conn_pool::SCOPES), + sub: None, + }; + let cred_file = CredentialsFile::new_from_str(cred).await.map_err(|e| { + SinkError::GooglePubSub( + anyhow!(e).context("Failed to create Google Cloud Pub/Sub credentials file"), + ) + })?; + let provider = + DefaultTokenSourceProvider::new_with_credentials(auth_config, Box::new(cred_file)) + .await + .map_err(|e| { + SinkError::GooglePubSub( + anyhow!(e).context( + "Failed to create Google Cloud Pub/Sub token source provider", + ), + ) + })?; + Environment::GoogleCloud(Box::new(provider)) + } else if let Some(emu_host) = config.emulator_host { + Environment::Emulator(emu_host) + } else { + return Err(SinkError::GooglePubSub(anyhow!( + "Missing emulator_host or credentials in Google Pub/Sub sink" + ))); + }; + + let client_config = ClientConfig { + endpoint: config.endpoint, + project_id: Some(config.project_id), + environment, + ..Default::default() + }; + let client = Client::new(client_config) + .await + .map_err(|e| SinkError::GooglePubSub(anyhow!(e)))?; + + let topic = async { + let topic = client.topic(&config.topic); + if !topic.exists(None).await? { + topic.create(None, None).await?; + } + Ok(topic) + } + .await + .map_err(|e: Status| SinkError::GooglePubSub(anyhow!(e)))?; + + let formatter = SinkFormatterImpl::new( + format_desc, + schema, + pk_indices, + db_name, + sink_from_name, + topic.fully_qualified_name(), + ) + .await?; + + let publisher = topic.new_publisher(None); + + Ok(Self { + formatter, + publisher, + }) + } +} + +pub struct GooglePubSubSinkWriter { + formatter: SinkFormatterImpl, + publisher: Publisher, +} + +impl AsyncTruncateSinkWriter for GooglePubSubSinkWriter { + type DeliveryFuture = GooglePubSubSinkDeliveryFuture; + + async fn write_chunk<'a>( + &'a mut self, + chunk: StreamChunk, + add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>, + ) -> Result<()> { + let mut payload_writer = GooglePubSubPayloadWriter { + publisher: &mut self.publisher, + message_vec: Vec::with_capacity(chunk.cardinality()), + add_future, + }; + dispatch_sink_formatter_str_key_impl!(&self.formatter, formatter, { + payload_writer.write_chunk(chunk, formatter).await + })?; + payload_writer.finish().await + } +} + +impl<'w> GooglePubSubPayloadWriter<'w> { + pub async fn finish(&mut self) -> Result<()> { + let message_vec = std::mem::take(&mut self.message_vec); + let awaiters = self.publisher.publish_bulk(message_vec).await; + self.add_future + .add_future_may_await(may_delivery_future(awaiters)) + .await?; + Ok(()) + } +} + +impl<'w> FormattedSink for GooglePubSubPayloadWriter<'w> { + type K = String; + type V = Vec; + + async fn write_one(&mut self, k: Option, v: Option) -> Result<()> { + let ordering_key = k.unwrap_or_default(); + match v { + Some(data) => { + let msg = PubsubMessage { + data, + ordering_key, + ..Default::default() + }; + self.message_vec.push(msg); + Ok(()) + } + None => Err(SinkError::GooglePubSub(anyhow!( + "Google Pub/Sub sink error: missing value to publish" + ))), + } + } +} diff --git a/src/connector/src/sink/iceberg/jni_catalog.rs b/src/connector/src/sink/iceberg/jni_catalog.rs index d88a63d398c65..6ef251878ff94 100644 --- a/src/connector/src/sink/iceberg/jni_catalog.rs +++ b/src/connector/src/sink/iceberg/jni_catalog.rs @@ -15,22 +15,40 @@ //! This module provide jni catalog. use std::collections::HashMap; +use std::fmt::Debug; use std::sync::Arc; use anyhow::Context; use async_trait::async_trait; +use iceberg::io::FileIO; +use iceberg::spec::TableMetadata; +use iceberg::table::Table as TableV2; +use iceberg::{ + Catalog as CatalogV2, Namespace, NamespaceIdent, TableCommit, TableCreation, TableIdent, +}; use icelake::catalog::models::{CommitTableRequest, CommitTableResponse, LoadTableResult}; use icelake::catalog::{ - BaseCatalogConfig, Catalog, CatalogRef, IcebergTableIoArgs, OperatorCreator, UpdateTable, + BaseCatalogConfig, Catalog, IcebergTableIoArgs, OperatorCreator, UpdateTable, }; use icelake::{ErrorKind, Table, TableIdentifier}; +use itertools::Itertools; use jni::objects::{GlobalRef, JObject}; use jni::JavaVM; use risingwave_jni_core::call_method; use risingwave_jni_core::jvm_runtime::{execute_with_jni_env, jobj_to_str, JVM}; +use serde::Deserialize; use crate::error::ConnectorResult; +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +struct LoadTableResponse { + pub metadata_location: Option, + pub metadata: TableMetadata, + pub _config: Option>, +} + +#[derive(Debug)] pub struct JniCatalog { java_catalog: GlobalRef, jvm: &'static JavaVM, @@ -138,13 +156,140 @@ impl Catalog for JniCatalog { } } +#[async_trait] +impl CatalogV2 for JniCatalog { + /// List namespaces from table. + async fn list_namespaces( + &self, + _parent: Option<&NamespaceIdent>, + ) -> iceberg::Result> { + todo!() + } + + /// Create a new namespace inside the catalog. + async fn create_namespace( + &self, + _namespace: &iceberg::NamespaceIdent, + _properties: HashMap, + ) -> iceberg::Result { + todo!() + } + + /// Get a namespace information from the catalog. + async fn get_namespace(&self, _namespace: &NamespaceIdent) -> iceberg::Result { + todo!() + } + + /// Check if namespace exists in catalog. + async fn namespace_exists(&self, _namespace: &NamespaceIdent) -> iceberg::Result { + todo!() + } + + /// Drop a namespace from the catalog. + async fn drop_namespace(&self, _namespace: &NamespaceIdent) -> iceberg::Result<()> { + todo!() + } + + /// List tables from namespace. + async fn list_tables(&self, _namespace: &NamespaceIdent) -> iceberg::Result> { + todo!() + } + + async fn update_namespace( + &self, + _namespace: &NamespaceIdent, + _properties: HashMap, + ) -> iceberg::Result<()> { + todo!() + } + + /// Create a new table inside the namespace. + async fn create_table( + &self, + _namespace: &NamespaceIdent, + _creation: TableCreation, + ) -> iceberg::Result { + todo!() + } + + /// Load table from the catalog. + async fn load_table(&self, table: &TableIdent) -> iceberg::Result { + execute_with_jni_env(self.jvm, |env| { + let table_name_str = format!( + "{}.{}", + table.namespace().clone().inner().into_iter().join("."), + table.name() + ); + + let table_name_jstr = env.new_string(&table_name_str).unwrap(); + + let result_json = + call_method!(env, self.java_catalog.as_obj(), {String loadTable(String)}, + &table_name_jstr) + .with_context(|| format!("Failed to load iceberg table: {table_name_str}"))?; + + let rust_json_str = jobj_to_str(env, result_json)?; + + let resp: LoadTableResponse = serde_json::from_str(&rust_json_str)?; + + let metadata_location = resp.metadata_location.ok_or_else(|| { + icelake::Error::new( + ErrorKind::IcebergFeatureUnsupported, + "Loading uncommitted table is not supported!", + ) + })?; + + tracing::info!("Table metadata location of {table_name_str} is {metadata_location}"); + + let table_metadata = resp.metadata; + + let file_io = FileIO::from_path(&metadata_location)? + .with_props(self.config.table_io_configs.iter()) + .build()?; + + Ok(TableV2::builder() + .file_io(file_io) + .identifier(table.clone()) + .metadata(table_metadata) + .build()) + }) + .map_err(|e| { + iceberg::Error::new( + iceberg::ErrorKind::Unexpected, + "Failed to load iceberg table.", + ) + .with_source(e) + }) + } + + /// Drop a table from the catalog. + async fn drop_table(&self, _table: &TableIdent) -> iceberg::Result<()> { + todo!() + } + + /// Check if a table exists in the catalog. + async fn table_exists(&self, _table: &TableIdent) -> iceberg::Result { + todo!() + } + + /// Rename a table in the catalog. + async fn rename_table(&self, _src: &TableIdent, _dest: &TableIdent) -> iceberg::Result<()> { + todo!() + } + + /// Update a table to the catalog. + async fn update_table(&self, _commit: TableCommit) -> iceberg::Result { + todo!() + } +} + impl JniCatalog { - pub fn build( + fn build( base_config: BaseCatalogConfig, name: impl ToString, catalog_impl: impl ToString, java_catalog_props: HashMap, - ) -> ConnectorResult { + ) -> ConnectorResult { let jvm = JVM.get_or_init()?; execute_with_jni_env(jvm, |env| { @@ -175,12 +320,32 @@ impl JniCatalog { let jni_catalog = env.new_global_ref(jni_catalog_wrapper.l().unwrap())?; - Ok(Arc::new(Self { + Ok(Self { java_catalog: jni_catalog, jvm, config: base_config, - }) as CatalogRef) + }) }) .map_err(Into::into) } + + pub fn build_catalog( + base_config: BaseCatalogConfig, + name: impl ToString, + catalog_impl: impl ToString, + java_catalog_props: HashMap, + ) -> ConnectorResult> { + let catalog = Self::build(base_config, name, catalog_impl, java_catalog_props)?; + Ok(Arc::new(catalog) as Arc) + } + + pub fn build_catalog_v2( + base_config: BaseCatalogConfig, + name: impl ToString, + catalog_impl: impl ToString, + java_catalog_props: HashMap, + ) -> ConnectorResult> { + let catalog = Self::build(base_config, name, catalog_impl, java_catalog_props)?; + Ok(Arc::new(catalog) as Arc) + } } diff --git a/src/connector/src/sink/iceberg/mod.rs b/src/connector/src/sink/iceberg/mod.rs index 22972d5629076..a63f5e49f0f80 100644 --- a/src/connector/src/sink/iceberg/mod.rs +++ b/src/connector/src/sink/iceberg/mod.rs @@ -13,21 +13,24 @@ // limitations under the License. mod jni_catalog; -mod log_sink; mod mock_catalog; mod prometheus; +mod storage_catalog; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt::Debug; use std::num::NonZeroU64; use std::ops::Deref; use std::sync::Arc; use anyhow::{anyhow, Context}; -use arrow_schema::{ +use arrow_schema_iceberg::{ DataType as ArrowDataType, Field as ArrowField, Fields, Schema as ArrowSchema, SchemaRef, }; use async_trait::async_trait; +use iceberg::io::{S3_ACCESS_KEY_ID, S3_ENDPOINT, S3_REGION, S3_SECRET_ACCESS_KEY}; +use iceberg::table::Table as TableV2; +use iceberg::{Catalog as CatalogV2, TableIdent}; use icelake::catalog::{ load_catalog, load_iceberg_base_catalog_config, BaseCatalogConfig, CatalogRef, CATALOG_NAME, CATALOG_TYPE, @@ -41,25 +44,25 @@ use icelake::transaction::Transaction; use icelake::types::{data_file_from_json, data_file_to_json, Any, DataFile}; use icelake::{Table, TableIdentifier}; use itertools::Itertools; -use risingwave_common::array::{ - iceberg_to_arrow_type, to_iceberg_record_batch_with_schema, Op, StreamChunk, -}; +use risingwave_common::array::arrow::IcebergArrowConvert; +use risingwave_common::array::{Op, StreamChunk}; use risingwave_common::bail; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::Schema; use risingwave_pb::connector_service::sink_metadata::Metadata::Serialized; use risingwave_pb::connector_service::sink_metadata::SerializedMetadata; use risingwave_pb::connector_service::SinkMetadata; use serde_derive::Deserialize; +use storage_catalog::StorageCatalogConfig; use thiserror_ext::AsReport; use url::Url; use with_options::WithOptions; -use self::log_sink::IcebergLogSinkerOf; use self::mock_catalog::MockCatalog; use self::prometheus::monitored_base_file_writer::MonitoredBaseFileWriterBuilder; use self::prometheus::monitored_position_delete_writer::MonitoredPositionDeleteWriterBuilder; use super::catalog::desc::SinkDesc; +use super::decouple_checkpoint_log_sink::DecoupleCheckpointLogSinkerOf; use super::{ Sink, SinkError, SinkWriterParam, SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, SINK_TYPE_UPSERT, }; @@ -136,7 +139,7 @@ pub struct IcebergConfig { } impl IcebergConfig { - pub fn from_hashmap(values: HashMap) -> Result { + pub fn from_btreemap(values: BTreeMap) -> Result { let mut config = serde_json::from_value::(serde_json::to_value(&values).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -305,10 +308,16 @@ impl IcebergConfig { iceberg_configs.insert(CATALOG_NAME.to_string(), self.catalog_name()); if let Some(region) = &self.region { + // icelake iceberg_configs.insert( "iceberg.table.io.region".to_string(), region.clone().to_string(), ); + // iceberg-rust + iceberg_configs.insert( + ("iceberg.table.io.".to_string() + S3_REGION).to_string(), + region.clone().to_string(), + ); } if let Some(endpoint) = &self.endpoint { @@ -316,8 +325,15 @@ impl IcebergConfig { "iceberg.table.io.endpoint".to_string(), endpoint.clone().to_string(), ); + + // iceberg-rust + iceberg_configs.insert( + ("iceberg.table.io.".to_string() + S3_ENDPOINT).to_string(), + endpoint.clone().to_string(), + ); } + // icelake iceberg_configs.insert( "iceberg.table.io.access_key_id".to_string(), self.access_key.clone().to_string(), @@ -327,6 +343,16 @@ impl IcebergConfig { self.secret_key.clone().to_string(), ); + // iceberg-rust + iceberg_configs.insert( + ("iceberg.table.io.".to_string() + S3_ACCESS_KEY_ID).to_string(), + self.access_key.clone().to_string(), + ); + iceberg_configs.insert( + ("iceberg.table.io.".to_string() + S3_SECRET_ACCESS_KEY).to_string(), + self.secret_key.clone().to_string(), + ); + let (bucket, _) = { let url = Url::parse(&self.path).map_err(|e| SinkError::Iceberg(anyhow!(e)))?; let bucket = url @@ -382,6 +408,31 @@ impl IcebergConfig { "s3.secret-access-key".to_string(), self.secret_key.clone().to_string(), ); + + if matches!(self.catalog_type.as_deref(), Some("glue")) { + java_catalog_configs.insert( + "client.credentials-provider".to_string(), + "com.risingwave.connector.catalog.GlueCredentialProvider".to_string(), + ); + // Use S3 ak/sk and region as glue ak/sk and region by default. + // TODO: use different ak/sk and region for s3 and glue. + java_catalog_configs.insert( + "client.credentials-provider.glue.access-key-id".to_string(), + self.access_key.clone().to_string(), + ); + java_catalog_configs.insert( + "client.credentials-provider.glue.secret-access-key".to_string(), + self.secret_key.clone().to_string(), + ); + if let Some(region) = &self.region { + java_catalog_configs + .insert("client.region".to_string(), region.clone().to_string()); + java_catalog_configs.insert( + "glue.endpoint".to_string(), + format!("https://glue.{}.amazonaws.com", region), + ); + } + } } Ok((base_catalog_config, java_catalog_configs)) @@ -394,16 +445,19 @@ impl IcebergConfig { let catalog = load_catalog(&iceberg_configs).await?; Ok(catalog) } - catalog_type if catalog_type == "hive" || catalog_type == "jdbc" => { + catalog_type + if catalog_type == "hive" || catalog_type == "jdbc" || catalog_type == "glue" => + { // Create java catalog let (base_catalog_config, java_catalog_props) = self.build_jni_catalog_configs()?; let catalog_impl = match catalog_type { "hive" => "org.apache.iceberg.hive.HiveCatalog", "jdbc" => "org.apache.iceberg.jdbc.JdbcCatalog", + "glue" => "org.apache.iceberg.aws.glue.GlueCatalog", _ => unreachable!(), }; - jni_catalog::JniCatalog::build( + jni_catalog::JniCatalog::build_catalog( base_catalog_config, self.catalog_name(), catalog_impl, @@ -413,7 +467,7 @@ impl IcebergConfig { "mock" => Ok(Arc::new(MockCatalog {})), _ => { bail!( - "Unsupported catalog type: {}, only support `storage`, `rest`, `hive`, `jdbc`", + "Unsupported catalog type: {}, only support `storage`, `rest`, `hive`, `jdbc`, `glue`", self.catalog_type() ) } @@ -434,6 +488,82 @@ impl IcebergConfig { } } +impl IcebergConfig { + fn full_table_name_v2(&self) -> Result { + let ret = if let Some(database_name) = &self.database_name { + TableIdent::from_strs(vec![database_name, &self.table_name]) + } else { + TableIdent::from_strs(vec![&self.table_name]) + }; + + ret.context("Failed to create table identifier") + .map_err(|e| SinkError::Iceberg(anyhow!(e))) + } + + async fn create_catalog_v2(&self) -> ConnectorResult> { + match self.catalog_type() { + "storage" => { + let config = StorageCatalogConfig::builder() + .warehouse(self.path.clone()) + .access_key(self.access_key.clone()) + .secret_key(self.secret_key.clone()) + .region(self.region.clone()) + .endpoint(self.endpoint.clone()) + .build(); + let catalog = storage_catalog::StorageCatalog::new(config)?; + Ok(Arc::new(catalog)) + } + "rest" => { + let config = iceberg_catalog_rest::RestCatalogConfig::builder() + .uri(self.uri.clone().ok_or_else(|| { + SinkError::Iceberg(anyhow!("`catalog.uri` must be set in rest catalog")) + })?) + .build(); + let catalog = iceberg_catalog_rest::RestCatalog::new(config).await?; + Ok(Arc::new(catalog)) + } + catalog_type + if catalog_type == "hive" || catalog_type == "jdbc" || catalog_type == "glue" => + { + // Create java catalog + let (base_catalog_config, java_catalog_props) = self.build_jni_catalog_configs()?; + let catalog_impl = match catalog_type { + "hive" => "org.apache.iceberg.hive.HiveCatalog", + "jdbc" => "org.apache.iceberg.jdbc.JdbcCatalog", + "glue" => "org.apache.iceberg.aws.glue.GlueCatalog", + _ => unreachable!(), + }; + + jni_catalog::JniCatalog::build_catalog_v2( + base_catalog_config, + self.catalog_name(), + catalog_impl, + java_catalog_props, + ) + } + _ => { + bail!( + "Unsupported catalog type: {}, only support `storage`, `rest`, `hive`, `jdbc`, `glue`", + self.catalog_type() + ) + } + } + } + + pub async fn load_table_v2(&self) -> ConnectorResult { + let catalog = self + .create_catalog_v2() + .await + .context("Unable to load iceberg catalog")?; + + let table_id = self + .full_table_name_v2() + .context("Unable to parse table name")?; + + catalog.load_table(&table_id).await.map_err(Into::into) + } +} + pub struct IcebergSink { config: IcebergConfig, param: SinkParam, @@ -445,7 +575,7 @@ impl TryFrom for IcebergSink { type Error = SinkError; fn try_from(param: SinkParam) -> std::result::Result { - let config = IcebergConfig::from_hashmap(param.properties.clone())?; + let config = IcebergConfig::from_btreemap(param.properties.clone())?; IcebergSink::new(config, param) } } @@ -475,7 +605,7 @@ impl IcebergSink { .try_into() .map_err(|err: icelake::Error| SinkError::Iceberg(anyhow!(err)))?; - try_matches_arrow_schema(&sink_schema, &iceberg_schema, false) + try_matches_arrow_schema(&sink_schema, &iceberg_schema) .map_err(|err| SinkError::Iceberg(anyhow!(err)))?; Ok(table) @@ -517,7 +647,7 @@ impl IcebergSink { impl Sink for IcebergSink { type Coordinator = IcebergSinkCommitter; - type LogSinker = IcebergLogSinkerOf>; + type LogSinker = DecoupleCheckpointLogSinkerOf>; const SINK_NAME: &'static str = ICEBERG_SINK; @@ -578,7 +708,7 @@ impl Sink for IcebergSink { "commit_checkpoint_interval should be greater than 0, and it should be checked in config validation", ); - Ok(IcebergLogSinkerOf::new( + Ok(DecoupleCheckpointLogSinkerOf::new( writer, writer_param.sink_metrics, commit_checkpoint_interval, @@ -797,14 +927,15 @@ impl SinkWriter for IcebergWriter { let filters = chunk.visibility() & ops.iter().map(|op| *op == Op::Insert).collect::(); chunk.set_visibility(filters); - let chunk = - to_iceberg_record_batch_with_schema(self.schema.clone(), &chunk.compact()) - .map_err(|err| SinkError::Iceberg(anyhow!(err)))?; + let chunk = IcebergArrowConvert + .to_record_batch(self.schema.clone(), &chunk.compact()) + .map_err(|err| SinkError::Iceberg(anyhow!(err)))?; writer.write(chunk).await?; } IcebergWriterEnum::Upsert(writer) => { - let chunk = to_iceberg_record_batch_with_schema(self.schema.clone(), &chunk) + let chunk = IcebergArrowConvert + .to_record_batch(self.schema.clone(), &chunk) .map_err(|err| SinkError::Iceberg(anyhow!(err)))?; writer @@ -1002,11 +1133,9 @@ impl SinkCommitCoordinator for IcebergSinkCommitter { } /// Try to match our schema with iceberg schema. -/// `for_source` = true means the schema is used for source, otherwise it's used for sink. pub fn try_matches_arrow_schema( rw_schema: &Schema, arrow_schema: &ArrowSchema, - for_source: bool, ) -> anyhow::Result<()> { if rw_schema.fields.len() != arrow_schema.fields().len() { bail!( @@ -1029,17 +1158,11 @@ pub fn try_matches_arrow_schema( .ok_or_else(|| anyhow!("Field {} not found in our schema", arrow_field.name()))?; // Iceberg source should be able to read iceberg decimal type. - // Since the arrow type default conversion is used by udf, in udf, decimal is converted to - // large binary type which is not compatible with iceberg decimal type, - // so we need to convert it to decimal type manually. - let converted_arrow_data_type = if for_source - && matches!(our_field_type, risingwave_common::types::DataType::Decimal) - { - // RisingWave decimal type cannot specify precision and scale, so we use the default value. - ArrowDataType::Decimal128(38, 0) - } else { - iceberg_to_arrow_type(our_field_type).map_err(|e| anyhow!(e))? - }; + let converted_arrow_data_type = IcebergArrowConvert + .to_arrow_field("", our_field_type) + .map_err(|e| anyhow!(e))? + .data_type() + .clone(); let compatible = match (&converted_arrow_data_type, arrow_field.data_type()) { (ArrowDataType::Decimal128(_, _), ArrowDataType::Decimal128(_, _)) => true, @@ -1057,7 +1180,7 @@ pub fn try_matches_arrow_schema( #[cfg(test)] mod test { - use std::collections::HashMap; + use std::collections::BTreeMap; use risingwave_common::catalog::Field; @@ -1066,7 +1189,7 @@ mod test { #[test] fn test_compatible_arrow_schema() { - use arrow_schema::{DataType as ArrowDataType, Field as ArrowField}; + use arrow_schema_iceberg::{DataType as ArrowDataType, Field as ArrowField}; use super::*; let risingwave_schema = Schema::new(vec![ @@ -1080,7 +1203,7 @@ mod test { ArrowField::new("c", ArrowDataType::Int32, false), ]); - try_matches_arrow_schema(&risingwave_schema, &arrow_schema, false).unwrap(); + try_matches_arrow_schema(&risingwave_schema, &arrow_schema).unwrap(); let risingwave_schema = Schema::new(vec![ Field::with_name(DataType::Int32, "d"), @@ -1094,7 +1217,7 @@ mod test { ArrowField::new("d", ArrowDataType::Int32, false), ArrowField::new("c", ArrowDataType::Int32, false), ]); - try_matches_arrow_schema(&risingwave_schema, &arrow_schema, false).unwrap(); + try_matches_arrow_schema(&risingwave_schema, &arrow_schema).unwrap(); } #[test] @@ -1120,7 +1243,7 @@ mod test { .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); - let iceberg_config = IcebergConfig::from_hashmap(values).unwrap(); + let iceberg_config = IcebergConfig::from_btreemap(values).unwrap(); let expected_iceberg_config = IcebergConfig { connector: "iceberg".to_string(), @@ -1152,8 +1275,8 @@ mod test { ); } - async fn test_create_catalog(configs: HashMap) { - let iceberg_config = IcebergConfig::from_hashmap(configs).unwrap(); + async fn test_create_catalog(configs: BTreeMap) { + let iceberg_config = IcebergConfig::from_btreemap(configs).unwrap(); let table = iceberg_config.load_table().await.unwrap(); diff --git a/src/connector/src/sink/iceberg/prometheus/monitored_base_file_writer.rs b/src/connector/src/sink/iceberg/prometheus/monitored_base_file_writer.rs index b7c04289b7590..3c205fd3b104e 100644 --- a/src/connector/src/sink/iceberg/prometheus/monitored_base_file_writer.rs +++ b/src/connector/src/sink/iceberg/prometheus/monitored_base_file_writer.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use arrow_array::RecordBatch; -use arrow_schema::SchemaRef; +use arrow_array_iceberg::RecordBatch; +use arrow_schema_iceberg::SchemaRef; use icelake::io_v2::{ BaseFileWriter, BaseFileWriterBuilder, BaseFileWriterMetrics, CurrentFileStatus, FileWriter, FileWriterBuilder, diff --git a/src/connector/src/sink/iceberg/prometheus/monitored_partition_writer.rs b/src/connector/src/sink/iceberg/prometheus/monitored_partition_writer.rs index c0bb5e097323c..d85d712c41ac3 100644 --- a/src/connector/src/sink/iceberg/prometheus/monitored_partition_writer.rs +++ b/src/connector/src/sink/iceberg/prometheus/monitored_partition_writer.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use arrow_schema::SchemaRef; +use arrow_schema_iceberg::SchemaRef; use icelake::io_v2::{ FanoutPartitionedWriter, FanoutPartitionedWriterBuilder, FanoutPartitionedWriterMetrics, IcebergWriter, IcebergWriterBuilder, @@ -27,6 +27,7 @@ pub struct MonitoredFanoutPartitionedWriterBuilder { } impl MonitoredFanoutPartitionedWriterBuilder { + #[expect(dead_code)] pub fn new( inner: FanoutPartitionedWriterBuilder, partition_num: LabelGuardedIntGauge<2>, @@ -74,7 +75,7 @@ impl MonitoredFanoutPartitionedWriter { impl IcebergWriter for MonitoredFanoutPartitionedWriter { type R = as IcebergWriter>::R; - async fn write(&mut self, batch: arrow_array::RecordBatch) -> Result<()> { + async fn write(&mut self, batch: arrow_array_iceberg::RecordBatch) -> Result<()> { self.inner.write(batch).await?; self.update_metrics()?; Ok(()) diff --git a/src/connector/src/sink/iceberg/prometheus/monitored_position_delete_writer.rs b/src/connector/src/sink/iceberg/prometheus/monitored_position_delete_writer.rs index e4fdc75f30e8b..43314c3fae384 100644 --- a/src/connector/src/sink/iceberg/prometheus/monitored_position_delete_writer.rs +++ b/src/connector/src/sink/iceberg/prometheus/monitored_position_delete_writer.rs @@ -43,7 +43,7 @@ impl IcebergWriterBuilder { type R = MonitoredPositionDeleteWriter; - async fn build(self, schema: &arrow_schema::SchemaRef) -> Result { + async fn build(self, schema: &arrow_schema_iceberg::SchemaRef) -> Result { let writer = self.inner.build(schema).await?; Ok(MonitoredPositionDeleteWriter { writer, diff --git a/src/connector/src/sink/iceberg/prometheus/monitored_write_writer.rs b/src/connector/src/sink/iceberg/prometheus/monitored_write_writer.rs index 0f2877490fb23..dc44434e5d9c2 100644 --- a/src/connector/src/sink/iceberg/prometheus/monitored_write_writer.rs +++ b/src/connector/src/sink/iceberg/prometheus/monitored_write_writer.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use arrow_array::RecordBatch; -use arrow_schema::SchemaRef; +use arrow_array_iceberg::RecordBatch; +use arrow_schema_iceberg::SchemaRef; use async_trait::async_trait; use icelake::io_v2::{IcebergWriter, IcebergWriterBuilder}; use icelake::Result; @@ -28,6 +28,7 @@ pub struct MonitoredWriteWriterBuilder { impl MonitoredWriteWriterBuilder { /// Create writer context. + #[expect(dead_code)] pub fn new( inner: B, write_qps: LabelGuardedIntCounter<2>, diff --git a/src/connector/src/sink/iceberg/storage_catalog.rs b/src/connector/src/sink/iceberg/storage_catalog.rs new file mode 100644 index 0000000000000..5fb2c5105fda5 --- /dev/null +++ b/src/connector/src/sink/iceberg/storage_catalog.rs @@ -0,0 +1,274 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +//! This module provide storage catalog. + +use std::collections::HashMap; + +use async_trait::async_trait; +use iceberg::io::{FileIO, S3_ACCESS_KEY_ID, S3_ENDPOINT, S3_REGION, S3_SECRET_ACCESS_KEY}; +use iceberg::spec::TableMetadata; +use iceberg::table::Table; +use iceberg::{ + Catalog, Error, ErrorKind, Namespace, NamespaceIdent, Result, TableCommit, TableCreation, + TableIdent, +}; +use opendal::Operator; +use thiserror_ext::AsReport; +use tokio_stream::StreamExt; +use typed_builder::TypedBuilder; + +#[derive(Clone, Debug, TypedBuilder)] +pub struct StorageCatalogConfig { + warehouse: String, + access_key: String, + secret_key: String, + endpoint: Option, + region: Option, +} + +/// File system catalog. +#[derive(Debug)] +pub struct StorageCatalog { + warehouse: String, + file_io: FileIO, + config: StorageCatalogConfig, +} + +impl StorageCatalog { + pub fn new(config: StorageCatalogConfig) -> Result { + let mut file_io_builder = FileIO::from_path(&config.warehouse)? + .with_prop(S3_ACCESS_KEY_ID, &config.access_key) + .with_prop(S3_SECRET_ACCESS_KEY, &config.secret_key); + file_io_builder = if let Some(endpoint) = &config.endpoint { + file_io_builder.with_prop(S3_ENDPOINT, endpoint) + } else { + file_io_builder + }; + file_io_builder = if let Some(region) = &config.region { + file_io_builder.with_prop(S3_REGION, region) + } else { + file_io_builder + }; + + Ok(StorageCatalog { + warehouse: config.warehouse.clone(), + file_io: file_io_builder.build()?, + config, + }) + } + + /// Check if version hint file exist. + /// + /// `table_path`: relative path of table dir under warehouse root. + async fn is_version_hint_exist(&self, table_path: &str) -> Result { + self.file_io + .is_exist(format!("{table_path}/metadata/version-hint.text").as_str()) + .await + .map_err(|err| { + Error::new( + ErrorKind::DataInvalid, + format!("check if version hint exist failed: {}", err.as_report()), + ) + }) + } + + /// Read version hint of table. + /// + /// `table_path`: relative path of table dir under warehouse root. + async fn read_version_hint(&self, table_path: &str) -> Result { + let content = self + .file_io + .new_input(format!("{table_path}/metadata/version-hint.text").as_str())? + .read() + .await?; + let version_hint = String::from_utf8(content.to_vec()).map_err(|err| { + Error::new( + ErrorKind::DataInvalid, + format!( + "Fail to covert version_hint from utf8 to string: {}", + err.as_report() + ), + ) + })?; + + version_hint + .parse() + .map_err(|_| Error::new(ErrorKind::DataInvalid, "parse version hint failed")) + } + + /// List all paths of table metadata files. + /// + /// The returned paths are sorted by name. + /// + /// TODO: we can improve this by only fetch the latest metadata. + /// + /// `table_path`: relative path of table dir under warehouse root. + async fn list_table_metadata_paths(&self, table_path: &str) -> Result> { + // create s3 operator + let mut builder = opendal::services::S3::default(); + builder + .root(&self.warehouse) + .access_key_id(&self.config.access_key) + .secret_access_key(&self.config.secret_key); + if let Some(endpoint) = &self.config.endpoint { + builder.endpoint(endpoint); + } + if let Some(region) = &self.config.region { + builder.region(region); + } + let op: Operator = Operator::new(builder) + .map_err(|err| Error::new(ErrorKind::Unexpected, err.to_report_string()))? + .finish(); + + // list metadata files + let mut lister = op + .lister(format!("{table_path}/metadata/").as_str()) + .await + .map_err(|err| { + Error::new( + ErrorKind::Unexpected, + format!("list metadata failed: {}", err.as_report()), + ) + })?; + let mut paths = vec![]; + while let Some(entry) = lister.next().await { + let entry = entry.map_err(|err| { + Error::new( + ErrorKind::Unexpected, + format!("list metadata entry failed: {}", err.as_report()), + ) + })?; + + // Only push into paths if the entry is a metadata file. + if entry.path().ends_with(".metadata.json") { + paths.push(entry.path().to_string()); + } + } + + // Make the returned paths sorted by name. + paths.sort(); + + Ok(paths) + } +} + +#[async_trait] +impl Catalog for StorageCatalog { + /// List namespaces from table. + async fn list_namespaces( + &self, + _parent: Option<&NamespaceIdent>, + ) -> iceberg::Result> { + todo!() + } + + /// Create a new namespace inside the catalog. + async fn create_namespace( + &self, + _namespace: &iceberg::NamespaceIdent, + _properties: HashMap, + ) -> iceberg::Result { + todo!() + } + + /// Get a namespace information from the catalog. + async fn get_namespace(&self, _namespace: &NamespaceIdent) -> iceberg::Result { + todo!() + } + + /// Check if namespace exists in catalog. + async fn namespace_exists(&self, _namespace: &NamespaceIdent) -> iceberg::Result { + todo!() + } + + /// Drop a namespace from the catalog. + async fn drop_namespace(&self, _namespace: &NamespaceIdent) -> iceberg::Result<()> { + todo!() + } + + /// List tables from namespace. + async fn list_tables(&self, _namespace: &NamespaceIdent) -> iceberg::Result> { + todo!() + } + + async fn update_namespace( + &self, + _namespace: &NamespaceIdent, + _properties: HashMap, + ) -> iceberg::Result<()> { + todo!() + } + + /// Create a new table inside the namespace. + async fn create_table( + &self, + _namespace: &NamespaceIdent, + _creation: TableCreation, + ) -> iceberg::Result
{ + todo!() + } + + /// Load table from the catalog. + async fn load_table(&self, table: &TableIdent) -> iceberg::Result
{ + let table_path = { + let mut names = table.namespace.clone().inner(); + names.push(table.name.to_string()); + format!("{}/{}", self.warehouse, names.join("/")) + }; + let path = if self.is_version_hint_exist(&table_path).await? { + let version_hint = self.read_version_hint(&table_path).await?; + format!("{table_path}/metadata/v{}.metadata.json", version_hint) + } else { + let files = self.list_table_metadata_paths(&table_path).await?; + + files.into_iter().last().ok_or(Error::new( + ErrorKind::DataInvalid, + "no table metadata found", + ))? + }; + + let metadata_file = self.file_io.new_input(path)?; + let metadata_file_content = metadata_file.read().await?; + let table_metadata = serde_json::from_slice::(&metadata_file_content)?; + + Ok(Table::builder() + .metadata(table_metadata) + .identifier(table.clone()) + .file_io(self.file_io.clone()) + // Only support readonly table for storage catalog now. + .readonly(true) + .build()) + } + + /// Drop a table from the catalog. + async fn drop_table(&self, _table: &TableIdent) -> iceberg::Result<()> { + todo!() + } + + /// Check if a table exists in the catalog. + async fn table_exists(&self, _table: &TableIdent) -> iceberg::Result { + todo!() + } + + /// Rename a table in the catalog. + async fn rename_table(&self, _src: &TableIdent, _dest: &TableIdent) -> iceberg::Result<()> { + todo!() + } + + /// Update a table to the catalog. + async fn update_table(&self, _commit: TableCommit) -> iceberg::Result
{ + todo!() + } +} diff --git a/src/connector/src/sink/kafka.rs b/src/connector/src/sink/kafka.rs index 8d56441e53488..617f427ae71f1 100644 --- a/src/connector/src/sink/kafka.rs +++ b/src/connector/src/sink/kafka.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use std::fmt::Debug; use std::sync::Arc; use std::time::Duration; @@ -35,7 +35,9 @@ use with_options::WithOptions; use super::catalog::{SinkFormat, SinkFormatDesc}; use super::{Sink, SinkError, SinkParam}; -use crate::connector_common::{KafkaCommon, KafkaPrivateLinkCommon, RdKafkaPropertiesCommon}; +use crate::connector_common::{ + AwsAuthProps, KafkaCommon, KafkaPrivateLinkCommon, RdKafkaPropertiesCommon, +}; use crate::sink::catalog::desc::SinkDesc; use crate::sink::formatter::SinkFormatterImpl; use crate::sink::log_store::DeliveryFutureManagerAddFuture; @@ -43,7 +45,9 @@ use crate::sink::writer::{ AsyncTruncateLogSinkerOf, AsyncTruncateSinkWriter, AsyncTruncateSinkWriterExt, FormattedSink, }; use crate::sink::{DummySinkCommitCoordinator, Result, SinkWriterParam}; -use crate::source::kafka::{KafkaProperties, KafkaSplitEnumerator, PrivateLinkProducerContext}; +use crate::source::kafka::{ + KafkaContextCommon, KafkaProperties, KafkaSplitEnumerator, RwProducerContext, +}; use crate::source::{SourceEnumeratorContext, SplitEnumerator}; use crate::{ deserialize_duration_from_string, deserialize_u32_from_string, dispatch_sink_formatter_impl, @@ -162,6 +166,10 @@ pub struct RdKafkaPropertiesProducer { )] #[serde_as(as = "DisplayFromStr")] max_in_flight_requests_per_connection: usize, + + #[serde(rename = "properties.request.required.acks")] + #[serde_as(as = "Option")] + request_required_acks: Option, } impl RdKafkaPropertiesProducer { @@ -196,6 +204,9 @@ impl RdKafkaPropertiesProducer { if let Some(v) = &self.compression_codec { c.set("compression.codec", v.to_string()); } + if let Some(v) = self.request_required_acks { + c.set("request.required.acks", v.to_string()); + } c.set("message.timeout.ms", self.message_timeout_ms.to_string()); c.set( "max.in.flight.requests.per.connection", @@ -237,10 +248,13 @@ pub struct KafkaConfig { #[serde(flatten)] pub privatelink_common: KafkaPrivateLinkCommon, + + #[serde(flatten)] + pub aws_auth_props: AwsAuthProps, } impl KafkaConfig { - pub fn from_hashmap(values: HashMap) -> Result { + pub fn from_btreemap(values: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(values).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -250,8 +264,6 @@ impl KafkaConfig { pub(crate) fn set_client(&self, c: &mut rdkafka::ClientConfig) { self.rdkafka_properties_common.set_client(c); self.rdkafka_properties_producer.set_client(c); - - tracing::info!("kafka client starts with: {:?}", c); } } @@ -267,6 +279,7 @@ impl From for KafkaProperties { rdkafka_properties_common: val.rdkafka_properties_common, rdkafka_properties_consumer: Default::default(), privatelink_common: val.privatelink_common, + aws_auth_props: val.aws_auth_props, unknown_fields: Default::default(), } } @@ -287,7 +300,7 @@ impl TryFrom for KafkaSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = KafkaConfig::from_hashmap(param.properties)?; + let config = KafkaConfig::from_btreemap(param.properties)?; Ok(Self { config, schema, @@ -307,11 +320,10 @@ impl Sink for KafkaSink { const SINK_NAME: &'static str = KAFKA_SINK; - fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + fn is_sink_decouple(_desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { match user_specified { - SinkDecouple::Default => Ok(desc.sink_type.is_append_only()), + SinkDecouple::Default | SinkDecouple::Enable => Ok(true), SinkDecouple::Disable => Ok(false), - SinkDecouple::Enable => Ok(true), } } @@ -386,7 +398,7 @@ const KAFKA_WRITER_MAX_QUEUE_SIZE_RATIO: f32 = 1.2; const KAFKA_WRITER_MAX_QUEUE_SIZE: usize = 100000; struct KafkaPayloadWriter<'a> { - inner: &'a FutureProducer, + inner: &'a FutureProducer, add_future: DeliveryFutureManagerAddFuture<'a, KafkaSinkDeliveryFuture>, config: &'a KafkaConfig, } @@ -395,13 +407,13 @@ pub type KafkaSinkDeliveryFuture = impl TryFuture + pub struct KafkaSinkWriter { formatter: SinkFormatterImpl, - inner: FutureProducer, + inner: FutureProducer, config: KafkaConfig, } impl KafkaSinkWriter { async fn new(config: KafkaConfig, formatter: SinkFormatterImpl) -> Result { - let inner: FutureProducer = { + let inner: FutureProducer = { let mut c = ClientConfig::new(); // KafkaConfig configuration @@ -412,13 +424,16 @@ impl KafkaSinkWriter { c.set("bootstrap.servers", &config.common.brokers); // Create the producer context, will be used to create the producer - let producer_ctx = PrivateLinkProducerContext::new( - config.privatelink_common.broker_rewrite_map.clone(), - // fixme: enable kafka native metrics for sink + let broker_rewrite_map = config.privatelink_common.broker_rewrite_map.clone(); + let ctx_common = KafkaContextCommon::new( + broker_rewrite_map, None, None, - )?; - + config.aws_auth_props.clone(), + config.common.is_aws_msk_iam(), + ) + .await?; + let producer_ctx = RwProducerContext::new(ctx_common); // Generate the producer c.create_with_context(producer_ctx).await? }; @@ -569,7 +584,7 @@ impl<'a> FormattedSink for KafkaPayloadWriter<'a> { #[cfg(test)] mod test { - use maplit::hashmap; + use maplit::btreemap; use risingwave_common::catalog::Field; use risingwave_common::types::DataType; @@ -582,7 +597,7 @@ mod test { #[test] fn parse_rdkafka_props() { - let props: HashMap = hashmap! { + let props: BTreeMap = btreemap! { // basic // "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), @@ -603,8 +618,9 @@ mod test { "properties.compression.codec".to_string() => "zstd".to_string(), "properties.message.timeout.ms".to_string() => "114514".to_string(), "properties.max.in.flight.requests.per.connection".to_string() => "114514".to_string(), + "properties.request.required.acks".to_string() => "-1".to_string(), }; - let c = KafkaConfig::from_hashmap(props).unwrap(); + let c = KafkaConfig::from_btreemap(props).unwrap(); assert_eq!( c.rdkafka_properties_producer.queue_buffering_max_ms, Some(114.514f64) @@ -619,8 +635,12 @@ mod test { .max_in_flight_requests_per_connection, 114514 ); + assert_eq!( + c.rdkafka_properties_producer.request_required_acks, + Some(-1) + ); - let props: HashMap = hashmap! { + let props: BTreeMap = btreemap! { // basic "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), @@ -629,9 +649,9 @@ mod test { "properties.enable.idempotence".to_string() => "True".to_string(), // can only be 'true' or 'false' }; - assert!(KafkaConfig::from_hashmap(props).is_err()); + assert!(KafkaConfig::from_btreemap(props).is_err()); - let props: HashMap = hashmap! { + let props: BTreeMap = btreemap! { // basic "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), @@ -639,9 +659,9 @@ mod test { "type".to_string() => "append-only".to_string(), "properties.queue.buffering.max.kbytes".to_string() => "-114514".to_string(), // usize cannot be negative }; - assert!(KafkaConfig::from_hashmap(props).is_err()); + assert!(KafkaConfig::from_btreemap(props).is_err()); - let props: HashMap = hashmap! { + let props: BTreeMap = btreemap! { // basic "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), @@ -649,12 +669,12 @@ mod test { "type".to_string() => "append-only".to_string(), "properties.compression.codec".to_string() => "notvalid".to_string(), // has to be a valid CompressionCodec }; - assert!(KafkaConfig::from_hashmap(props).is_err()); + assert!(KafkaConfig::from_btreemap(props).is_err()); } #[test] fn parse_kafka_config() { - let properties: HashMap = hashmap! { + let properties: BTreeMap = btreemap! { // "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), "topic".to_string() => "test".to_string(), @@ -669,48 +689,48 @@ mod test { // PrivateLink "broker.rewrite.endpoints".to_string() => "{\"broker1\": \"10.0.0.1:8001\"}".to_string(), }; - let config = KafkaConfig::from_hashmap(properties).unwrap(); + let config = KafkaConfig::from_btreemap(properties).unwrap(); assert_eq!(config.common.brokers, "localhost:9092"); assert_eq!(config.common.topic, "test"); assert_eq!(config.max_retry_num, 20); assert_eq!(config.retry_interval, Duration::from_millis(500)); // PrivateLink fields - let hashmap: HashMap = hashmap! { + let btreemap: BTreeMap = btreemap! { "broker1".to_string() => "10.0.0.1:8001".to_string() }; - assert_eq!(config.privatelink_common.broker_rewrite_map, Some(hashmap)); + assert_eq!(config.privatelink_common.broker_rewrite_map, Some(btreemap)); // Optional fields eliminated. - let properties: HashMap = hashmap! { + let properties: BTreeMap = btreemap! { // "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), "topic".to_string() => "test".to_string(), // "type".to_string() => "upsert".to_string(), }; - let config = KafkaConfig::from_hashmap(properties).unwrap(); + let config = KafkaConfig::from_btreemap(properties).unwrap(); assert_eq!(config.max_retry_num, 3); assert_eq!(config.retry_interval, Duration::from_millis(100)); // Invalid u32 input. - let properties: HashMap = hashmap! { + let properties: BTreeMap = btreemap! { "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), "topic".to_string() => "test".to_string(), "type".to_string() => "upsert".to_string(), "properties.retry.max".to_string() => "-20".to_string(), // error! }; - assert!(KafkaConfig::from_hashmap(properties).is_err()); + assert!(KafkaConfig::from_btreemap(properties).is_err()); // Invalid duration input. - let properties: HashMap = hashmap! { + let properties: BTreeMap = btreemap! { "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:9092".to_string(), "topic".to_string() => "test".to_string(), "type".to_string() => "upsert".to_string(), "properties.retry.interval".to_string() => "500minutes".to_string(), // error! }; - assert!(KafkaConfig::from_hashmap(properties).is_err()); + assert!(KafkaConfig::from_btreemap(properties).is_err()); } /// Note: Please enable the kafka by running `./risedev configure` before commenting #[ignore] @@ -719,7 +739,7 @@ mod test { #[tokio::test] async fn test_kafka_producer() -> Result<()> { // Create a dummy kafka properties - let properties = hashmap! { + let properties = btreemap! { "connector".to_string() => "kafka".to_string(), "properties.bootstrap.server".to_string() => "localhost:29092".to_string(), "type".to_string() => "append-only".to_string(), @@ -743,7 +763,7 @@ mod test { }, ]); - let kafka_config = KafkaConfig::from_hashmap(properties)?; + let kafka_config = KafkaConfig::from_btreemap(properties)?; // Create the actual sink writer to Kafka let sink = KafkaSinkWriter::new( diff --git a/src/connector/src/sink/kinesis.rs b/src/connector/src/sink/kinesis.rs index 3b5533a49c76c..771d3c8a6f91d 100644 --- a/src/connector/src/sink/kinesis.rs +++ b/src/connector/src/sink/kinesis.rs @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use anyhow::{anyhow, Context}; -use aws_sdk_kinesis::operation::put_record::PutRecordOutput; +use aws_sdk_kinesis::operation::put_records::builders::PutRecordsFluentBuilder; use aws_sdk_kinesis::primitives::Blob; +use aws_sdk_kinesis::types::PutRecordsRequestEntry; use aws_sdk_kinesis::Client as KinesisClient; +use futures::{FutureExt, TryFuture}; use risingwave_common::array::StreamChunk; use risingwave_common::catalog::Schema; use risingwave_common::session_config::sink_decouple::SinkDecouple; @@ -56,7 +58,7 @@ impl TryFrom for KinesisSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = KinesisSinkConfig::from_hashmap(param.properties)?; + let config = KinesisSinkConfig::from_btreemap(param.properties)?; Ok(Self { config, schema, @@ -70,17 +72,18 @@ impl TryFrom for KinesisSink { } } +const KINESIS_SINK_MAX_PENDING_CHUNK_NUM: usize = 64; + impl Sink for KinesisSink { type Coordinator = DummySinkCommitCoordinator; type LogSinker = AsyncTruncateLogSinkerOf; const SINK_NAME: &'static str = KINESIS_SINK; - fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + fn is_sink_decouple(_desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { match user_specified { - SinkDecouple::Default => Ok(desc.sink_type.is_append_only()), + SinkDecouple::Default | SinkDecouple::Enable => Ok(true), SinkDecouple::Disable => Ok(false), - SinkDecouple::Enable => Ok(true), } } @@ -125,7 +128,7 @@ impl Sink for KinesisSink { self.sink_from_name.clone(), ) .await? - .into_log_sinker(usize::MAX)) + .into_log_sinker(KINESIS_SINK_MAX_PENDING_CHUNK_NUM)) } } @@ -137,7 +140,7 @@ pub struct KinesisSinkConfig { } impl KinesisSinkConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(properties).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -148,12 +151,13 @@ impl KinesisSinkConfig { pub struct KinesisSinkWriter { pub config: KinesisSinkConfig, formatter: SinkFormatterImpl, - payload_writer: KinesisSinkPayloadWriter, + client: KinesisClient, } struct KinesisSinkPayloadWriter { - client: KinesisClient, - config: KinesisSinkConfig, + // builder should always be `Some`. Making it an option so that we can call + // builder methods that take the builder ownership as input and return with a new builder. + builder: Option, } impl KinesisSinkWriter { @@ -182,29 +186,57 @@ impl KinesisSinkWriter { Ok(Self { config: config.clone(), formatter, - payload_writer: KinesisSinkPayloadWriter { client, config }, + client, }) } + + fn new_payload_writer(&self) -> KinesisSinkPayloadWriter { + let builder = self + .client + .put_records() + .stream_name(&self.config.common.stream_name); + KinesisSinkPayloadWriter { + builder: Some(builder), + } + } } + +pub type KinesisSinkPayloadWriterDeliveryFuture = + impl TryFuture + Unpin + Send + 'static; + impl KinesisSinkPayloadWriter { - async fn put_record(&self, key: &str, payload: Vec) -> Result { - let payload = Blob::new(payload); - // todo: switch to put_records() for batching - Retry::spawn( - ExponentialBackoff::from_millis(100).map(jitter).take(3), - || async { - self.client - .put_record() - .stream_name(&self.config.common.stream_name) + fn put_record(&mut self, key: String, payload: Vec) { + self.builder = Some( + self.builder.take().expect("should not be None").records( + PutRecordsRequestEntry::builder() .partition_key(key) - .data(payload.clone()) - .send() - .await - }, - ) - .await - .with_context(|| format!("failed to put record to {}", self.config.common.stream_name)) - .map_err(SinkError::Kinesis) + .data(Blob::new(payload)) + .build() + .expect("should not fail because we have set `data` and `partition_key`"), + ), + ); + } + + fn finish(self) -> KinesisSinkPayloadWriterDeliveryFuture { + async move { + let builder = self.builder.expect("should not be None"); + let context_fmt = format!( + "failed to put record to {}", + builder + .get_stream_name() + .as_ref() + .expect("should have set stream name") + ); + Retry::spawn( + ExponentialBackoff::from_millis(100).map(jitter).take(3), + || builder.clone().send(), + ) + .await + .with_context(|| context_fmt.clone()) + .map_err(SinkError::Kinesis)?; + Ok(()) + } + .boxed() } } @@ -214,24 +246,46 @@ impl FormattedSink for KinesisSinkPayloadWriter { async fn write_one(&mut self, k: Option, v: Option) -> Result<()> { self.put_record( - &k.ok_or_else(|| SinkError::Kinesis(anyhow!("no key provided")))?, + k.ok_or_else(|| SinkError::Kinesis(anyhow!("no key provided")))?, v.unwrap_or_default(), - ) - .await - .map(|_| ()) + ); + Ok(()) } } impl AsyncTruncateSinkWriter for KinesisSinkWriter { + type DeliveryFuture = KinesisSinkPayloadWriterDeliveryFuture; + async fn write_chunk<'a>( &'a mut self, chunk: StreamChunk, - _add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>, + mut add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>, ) -> Result<()> { + let mut payload_writer = self.new_payload_writer(); dispatch_sink_formatter_str_key_impl!( &self.formatter, formatter, - self.payload_writer.write_chunk(chunk, formatter).await - ) + payload_writer.write_chunk(chunk, formatter).await + )?; + + add_future + .add_future_may_await(payload_writer.finish()) + .await?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use aws_sdk_kinesis::types::PutRecordsRequestEntry; + use aws_smithy_types::Blob; + + #[test] + fn test_kinesis_entry_builder_save_unwrap() { + PutRecordsRequestEntry::builder() + .data(Blob::new(b"data")) + .partition_key("partition-key") + .build() + .unwrap(); } } diff --git a/src/connector/src/sink/log_store.rs b/src/connector/src/sink/log_store.rs index c2cdf7756c598..d2609e404cc56 100644 --- a/src/connector/src/sink/log_store.rs +++ b/src/connector/src/sink/log_store.rs @@ -18,11 +18,14 @@ use std::fmt::Debug; use std::future::{poll_fn, Future}; use std::sync::Arc; use std::task::Poll; +use std::time::Instant; +use await_tree::InstrumentAwait; use futures::{TryFuture, TryFutureExt}; use risingwave_common::array::StreamChunk; use risingwave_common::bail; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; +use risingwave_common::metrics::LabelGuardedIntCounter; use risingwave_common::util::epoch::{EpochPair, INVALID_EPOCH}; use crate::sink::SinkMetrics; @@ -161,10 +164,7 @@ pub trait LogReader: Send + Sized + 'static { /// Mark that all items emitted so far have been consumed and it is safe to truncate the log /// from the current offset. - fn truncate( - &mut self, - offset: TruncateOffset, - ) -> impl Future> + Send + '_; + fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()>; /// Reset the log reader to after the latest truncate offset /// @@ -205,10 +205,7 @@ impl StreamChunk + Send + 'static, R: LogReader> LogReader Ok((epoch, item)) } - fn truncate( - &mut self, - offset: TruncateOffset, - ) -> impl Future> + Send + '_ { + fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()> { self.inner.truncate(offset) } @@ -219,39 +216,102 @@ impl StreamChunk + Send + 'static, R: LogReader> LogReader } } +pub struct BackpressureMonitoredLogReader { + inner: R, + /// Start time to wait for new future after poll ready + wait_new_future_start_time: Option, + wait_new_future_duration_ns: LabelGuardedIntCounter<3>, +} + +impl BackpressureMonitoredLogReader { + fn new(inner: R, wait_new_future_duration_ns: LabelGuardedIntCounter<3>) -> Self { + Self { + inner, + wait_new_future_start_time: None, + wait_new_future_duration_ns, + } + } +} + +impl LogReader for BackpressureMonitoredLogReader { + fn init(&mut self) -> impl Future> + Send + '_ { + self.wait_new_future_start_time = None; + self.inner.init() + } + + fn next_item( + &mut self, + ) -> impl Future> + Send + '_ { + if let Some(start_time) = self.wait_new_future_start_time.take() { + self.wait_new_future_duration_ns + .inc_by(start_time.elapsed().as_nanos() as _); + } + self.inner.next_item().inspect_ok(|_| { + // Set start time when return ready + self.wait_new_future_start_time = Some(Instant::now()); + }) + } + + fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()> { + self.inner.truncate(offset) + } + + fn rewind( + &mut self, + ) -> impl Future)>> + Send + '_ { + self.inner.rewind().inspect_ok(|_| { + self.wait_new_future_start_time = None; + }) + } +} + pub struct MonitoredLogReader { inner: R, read_epoch: u64, metrics: SinkMetrics, } +impl MonitoredLogReader { + pub fn new(inner: R, metrics: SinkMetrics) -> Self { + Self { + inner, + read_epoch: INVALID_EPOCH, + metrics, + } + } +} + impl LogReader for MonitoredLogReader { async fn init(&mut self) -> LogStoreResult<()> { - self.inner.init().await + self.inner.init().instrument_await("log_reader_init").await } async fn next_item(&mut self) -> LogStoreResult<(u64, LogStoreReadItem)> { - self.inner.next_item().await.inspect(|(epoch, item)| { - if self.read_epoch != *epoch { - self.read_epoch = *epoch; - self.metrics.log_store_latest_read_epoch.set(*epoch as _); - } - if let LogStoreReadItem::StreamChunk { chunk, .. } = item { - self.metrics - .log_store_read_rows - .inc_by(chunk.cardinality() as _); - } - }) + self.inner + .next_item() + .instrument_await("log_reader_next_item") + .await + .inspect(|(epoch, item)| { + if self.read_epoch != *epoch { + self.read_epoch = *epoch; + self.metrics.log_store_latest_read_epoch.set(*epoch as _); + } + if let LogStoreReadItem::StreamChunk { chunk, .. } = item { + self.metrics + .log_store_read_rows + .inc_by(chunk.cardinality() as _); + } + }) } - async fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()> { - self.inner.truncate(offset).await + fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()> { + self.inner.truncate(offset) } fn rewind( &mut self, ) -> impl Future)>> + Send + '_ { - self.inner.rewind() + self.inner.rewind().instrument_await("log_reader_rewind") } } @@ -267,12 +327,12 @@ where TransformChunkLogReader { f, inner: self } } - pub fn monitored(self, metrics: SinkMetrics) -> MonitoredLogReader { - MonitoredLogReader { - read_epoch: INVALID_EPOCH, - inner: self, - metrics, - } + pub fn monitored(self, metrics: SinkMetrics) -> impl LogReader { + let wait_new_future_duration = metrics.log_store_reader_wait_new_future_duration_ns.clone(); + BackpressureMonitoredLogReader::new( + MonitoredLogReader::new(self, metrics), + wait_new_future_duration, + ) } } diff --git a/src/connector/src/sink/mock_coordination_client.rs b/src/connector/src/sink/mock_coordination_client.rs index 8089a99e32ec0..61a1a163171b2 100644 --- a/src/connector/src/sink/mock_coordination_client.rs +++ b/src/connector/src/sink/mock_coordination_client.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_pb::connector_service::coordinate_response::{ self, CommitResponse, StartCoordinationResponse, }; diff --git a/src/connector/src/sink/mod.rs b/src/connector/src/sink/mod.rs index c430b4303f1e9..872e7bbfeaf7c 100644 --- a/src/connector/src/sink/mod.rs +++ b/src/connector/src/sink/mod.rs @@ -17,17 +17,21 @@ pub mod boxed; pub mod catalog; pub mod clickhouse; pub mod coordinate; +pub mod decouple_checkpoint_log_sink; pub mod deltalake; pub mod doris; pub mod doris_starrocks_connector; +pub mod dynamodb; pub mod elasticsearch; pub mod encoder; pub mod formatter; +pub mod google_pubsub; pub mod iceberg; pub mod kafka; pub mod kinesis; pub mod log_store; pub mod mock_coordination_client; +pub mod mongodb; pub mod mqtt; pub mod nats; pub mod pulsar; @@ -35,13 +39,14 @@ pub mod redis; pub mod remote; pub mod snowflake; pub mod snowflake_connector; +pub mod sqlserver; pub mod starrocks; pub mod test_sink; pub mod trivial; pub mod utils; pub mod writer; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::future::Future; use ::clickhouse::error::Error as ClickHouseError; @@ -49,7 +54,7 @@ use ::deltalake::DeltaTableError; use ::redis::RedisError; use anyhow::anyhow; use async_trait::async_trait; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::{ColumnDesc, Field, Schema}; use risingwave_common::metrics::{ LabelGuardedHistogram, LabelGuardedIntCounter, LabelGuardedIntGauge, @@ -70,7 +75,6 @@ use crate::sink::catalog::desc::SinkDesc; use crate::sink::catalog::{SinkCatalog, SinkId}; use crate::sink::log_store::{LogReader, LogStoreReadItem, LogStoreResult, TruncateOffset}; use crate::sink::writer::SinkWriter; -use crate::ConnectorParams; const BOUNDED_CHANNEL_SIZE: usize = 16; #[macro_export] @@ -86,9 +90,11 @@ macro_rules! for_all_sinks { { ClickHouse, $crate::sink::clickhouse::ClickHouseSink }, { Iceberg, $crate::sink::iceberg::IcebergSink }, { Mqtt, $crate::sink::mqtt::MqttSink }, + { GooglePubSub, $crate::sink::google_pubsub::GooglePubSubSink }, { Nats, $crate::sink::nats::NatsSink }, { Jdbc, $crate::sink::remote::JdbcSink }, { ElasticSearch, $crate::sink::remote::ElasticSearchSink }, + { Opensearch, $crate::sink::remote::OpensearchSink }, { Cassandra, $crate::sink::remote::CassandraSink }, { HttpJava, $crate::sink::remote::HttpJavaSink }, { Doris, $crate::sink::doris::DorisSink }, @@ -96,6 +102,9 @@ macro_rules! for_all_sinks { { Snowflake, $crate::sink::snowflake::SnowflakeSink }, { DeltaLake, $crate::sink::deltalake::DeltaLakeSink }, { BigQuery, $crate::sink::big_query::BigQuerySink }, + { DynamoDb, $crate::sink::dynamodb::DynamoDbSink }, + { Mongodb, $crate::sink::mongodb::MongodbSink }, + { SqlServer, $crate::sink::sqlserver::SqlServerSink }, { Test, $crate::sink::test_sink::TestSink }, { Table, $crate::sink::trivial::TableSink } } @@ -153,7 +162,7 @@ pub const SINK_USER_FORCE_APPEND_ONLY_OPTION: &str = "force_append_only"; pub struct SinkParam { pub sink_id: SinkId, pub sink_name: String, - pub properties: HashMap, + pub properties: BTreeMap, pub columns: Vec, pub downstream_pk: Vec, pub sink_type: SinkType, @@ -254,6 +263,8 @@ pub struct SinkMetrics { pub log_store_latest_read_epoch: LabelGuardedIntGauge<3>, pub log_store_read_rows: LabelGuardedIntCounter<3>, + pub log_store_reader_wait_new_future_duration_ns: LabelGuardedIntCounter<3>, + pub iceberg_write_qps: LabelGuardedIntCounter<2>, pub iceberg_write_latency: LabelGuardedHistogram<2>, pub iceberg_rolling_unflushed_data_file: LabelGuardedIntGauge<2>, @@ -271,6 +282,8 @@ impl SinkMetrics { log_store_latest_read_epoch: LabelGuardedIntGauge::test_int_gauge(), log_store_write_rows: LabelGuardedIntCounter::test_int_counter(), log_store_read_rows: LabelGuardedIntCounter::test_int_counter(), + log_store_reader_wait_new_future_duration_ns: LabelGuardedIntCounter::test_int_counter( + ), iceberg_write_qps: LabelGuardedIntCounter::test_int_counter(), iceberg_write_latency: LabelGuardedHistogram::test_histogram(), iceberg_rolling_unflushed_data_file: LabelGuardedIntGauge::test_int_gauge(), @@ -282,7 +295,6 @@ impl SinkMetrics { #[derive(Clone)] pub struct SinkWriterParam { - pub connector_params: ConnectorParams, pub executor_id: u64, pub vnode_bitmap: Option, pub meta_client: Option, @@ -320,7 +332,6 @@ impl SinkMetaClient { impl SinkWriterParam { pub fn for_test() -> Self { SinkWriterParam { - connector_params: Default::default(), executor_id: Default::default(), vnode_bitmap: Default::default(), meta_client: Default::default(), @@ -361,10 +372,7 @@ pub trait SinkLogReader: Send + Sized + 'static { /// Mark that all items emitted so far have been consumed and it is safe to truncate the log /// from the current offset. - fn truncate( - &mut self, - offset: TruncateOffset, - ) -> impl Future> + Send + '_; + fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()>; } impl SinkLogReader for R { @@ -374,17 +382,14 @@ impl SinkLogReader for R { ::next_item(self) } - fn truncate( - &mut self, - offset: TruncateOffset, - ) -> impl Future> + Send + '_ { + fn truncate(&mut self, offset: TruncateOffset) -> LogStoreResult<()> { ::truncate(self, offset) } } #[async_trait] pub trait LogSinker: 'static { - async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result<()>; + async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result; } #[async_trait] @@ -441,6 +446,10 @@ impl SinkImpl { pub fn is_sink_into_table(&self) -> bool { matches!(self, SinkImpl::Table(_)) } + + pub fn is_blackhole(&self) -> bool { + matches!(self, SinkImpl::BlackHole(_)) + } } pub fn build_sink(param: SinkParam) -> Result { @@ -525,6 +534,12 @@ pub enum SinkError { #[backtrace] anyhow::Error, ), + #[error("Google Pub/Sub error: {0}")] + GooglePubSub( + #[source] + #[backtrace] + anyhow::Error, + ), #[error("Doris/Starrocks connect error: {0}")] DorisStarrocksConnect( #[source] @@ -542,7 +557,11 @@ pub enum SinkError { #[error("Starrocks error: {0}")] Starrocks(String), #[error("Snowflake error: {0}")] - Snowflake(String), + Snowflake( + #[source] + #[backtrace] + anyhow::Error, + ), #[error("Pulsar error: {0}")] Pulsar( #[source] @@ -561,12 +580,30 @@ pub enum SinkError { #[backtrace] anyhow::Error, ), + #[error("DynamoDB error: {0}")] + DynamoDb( + #[source] + #[backtrace] + anyhow::Error, + ), + #[error("SQL Server error: {0}")] + SqlServer( + #[source] + #[backtrace] + anyhow::Error, + ), #[error(transparent)] Connector( #[from] #[backtrace] ConnectorError, ), + #[error("Mongodb error: {0}")] + Mongodb( + #[source] + #[backtrace] + anyhow::Error, + ), } impl From for SinkError { @@ -598,3 +635,9 @@ impl From for SinkError { SinkError::Redis(value.to_report_string()) } } + +impl From for SinkError { + fn from(err: tiberius::error::Error) -> Self { + SinkError::SqlServer(anyhow!(err)) + } +} diff --git a/src/connector/src/sink/mongodb.rs b/src/connector/src/sink/mongodb.rs new file mode 100644 index 0000000000000..8840c72176960 --- /dev/null +++ b/src/connector/src/sink/mongodb.rs @@ -0,0 +1,762 @@ +// Copyright 2024 RisingWave Labs +// +// 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::{BTreeMap, HashMap}; +use std::ops::Deref; +use std::sync::LazyLock; + +use anyhow::anyhow; +use itertools::Itertools; +use mongodb::bson::{bson, doc, Array, Bson, Document}; +use mongodb::{Client, Namespace}; +use risingwave_common::array::{Op, RowRef, StreamChunk}; +use risingwave_common::catalog::Schema; +use risingwave_common::log::LogSuppresser; +use risingwave_common::must_match; +use risingwave_common::row::Row; +use risingwave_common::session_config::sink_decouple::SinkDecouple; +use risingwave_common::types::ScalarRefImpl; +use serde_derive::Deserialize; +use serde_with::{serde_as, DisplayFromStr}; +use thiserror_ext::AsReport; +use tonic::async_trait; +use with_options::WithOptions; + +use super::catalog::desc::SinkDesc; +use super::encoder::BsonEncoder; +use crate::connector_common::MongodbCommon; +use crate::deserialize_bool_from_string; +use crate::sink::encoder::RowEncoder; +use crate::sink::writer::{LogSinkerOf, SinkWriter, SinkWriterExt}; +use crate::sink::{ + DummySinkCommitCoordinator, Result, Sink, SinkError, SinkParam, SinkWriterParam, + SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, SINK_TYPE_UPSERT, +}; + +pub const MONGODB_SINK: &str = "mongodb"; + +// 65536 seems like a reasonable limit, but we may consider setting this limit to 100,000, +// which is the actual limit imposed by the server. +// see https://www.mongodb.com/docs/v4.2/reference/command/hello/#hello.maxWriteBatchSize for more details +pub const MONGODB_BULK_WRITE_SIZE_LIMIT: usize = 65536; +pub const MONGODB_PK_NAME: &str = "_id"; + +static LOG_SUPPERSSER: LazyLock = LazyLock::new(LogSuppresser::default); + +const fn _default_bulk_write_max_entries() -> usize { + 1024 +} + +#[serde_as] +#[derive(Clone, Debug, Deserialize, WithOptions)] +pub struct MongodbConfig { + #[serde(flatten)] + pub common: MongodbCommon, + + pub r#type: String, // accept "append-only" or "upsert" + + /// The dynamic collection name where data should be sunk to. If specified, the field value will be used + /// as the collection name. The collection name format is same as `collection.name`. If the field value is + /// null or an empty string, then the `collection.name` will be used as a fallback destination. + #[serde(rename = "collection.name.field")] + pub collection_name_field: Option, + + /// Controls whether the field value of `collection.name.field` should be dropped when sinking. + /// Set this option to true to avoid the duplicate values of `collection.name.field` being written to the + /// result collection. + #[serde( + default, + deserialize_with = "deserialize_bool_from_string", + rename = "collection.name.field.drop" + )] + pub drop_collection_name_field: bool, + + /// The maximum entries will accumulate before performing the bulk write, defaults to 1024. + #[serde( + rename = "mongodb.bulk_write.max_entries", + default = "_default_bulk_write_max_entries" + )] + #[serde_as(as = "DisplayFromStr")] + pub bulk_write_max_entries: usize, +} + +impl MongodbConfig { + pub fn from_btreemap(properties: BTreeMap) -> crate::sink::Result { + let config = + serde_json::from_value::(serde_json::to_value(properties).unwrap()) + .map_err(|e| SinkError::Config(anyhow!(e)))?; + if config.r#type != SINK_TYPE_APPEND_ONLY && config.r#type != SINK_TYPE_UPSERT { + return Err(SinkError::Config(anyhow!( + "`{}` must be {}, or {}", + SINK_TYPE_OPTION, + SINK_TYPE_APPEND_ONLY, + SINK_TYPE_UPSERT + ))); + } + Ok(config) + } +} + +/// An async-drop style `Guard` for `mongodb::Client`. Use this guard to hold a client, +/// the `client::shutdown` is called in an async manner when the guard is dropped. +/// Please be aware this is a "best effort" style shutdown, which may not be successful if the +/// tokio runtime is in the process of terminating. However, the server-side resources will be +/// cleaned up eventually due to the session expiration. +/// see [this issue](https://github.com/mongodb/mongo-rust-driver/issues/719) for more information +struct ClientGuard { + _tx: tokio::sync::oneshot::Sender<()>, + client: Client, +} + +impl ClientGuard { + fn new(name: String, client: Client) -> Self { + let client_copy = client.clone(); + let (_tx, rx) = tokio::sync::oneshot::channel::<()>(); + tokio::spawn(async move { + tracing::debug!(%name, "waiting for client to shut down"); + let _ = rx.await; + tracing::debug!(%name, "sender dropped now calling client's shutdown"); + // shutdown may stuck if the resources created by client are not dropped at this point. + // As recommended by [shutdown](https://docs.rs/mongodb/2.8.2/mongodb/struct.Client.html#method.shutdown) + // documentation, we should make our resources usage shorter-lived than the client. So if this happens, + // there are some programming error in our code. + client_copy.shutdown().await; + tracing::debug!(%name, "client shutdown succeeded"); + }); + Self { _tx, client } + } +} + +impl Deref for ClientGuard { + type Target = Client; + + fn deref(&self) -> &Self::Target { + &self.client + } +} + +#[derive(Debug)] +pub struct MongodbSink { + pub config: MongodbConfig, + param: SinkParam, + schema: Schema, + pk_indices: Vec, + is_append_only: bool, +} + +impl MongodbSink { + pub fn new(param: SinkParam) -> Result { + let config = MongodbConfig::from_btreemap(param.properties.clone())?; + let pk_indices = param.downstream_pk.clone(); + let is_append_only = param.sink_type.is_append_only(); + let schema = param.schema(); + Ok(Self { + config, + param, + schema, + pk_indices, + is_append_only, + }) + } +} + +impl TryFrom for MongodbSink { + type Error = SinkError; + + fn try_from(param: SinkParam) -> std::result::Result { + MongodbSink::new(param) + } +} + +impl Sink for MongodbSink { + type Coordinator = DummySinkCommitCoordinator; + type LogSinker = LogSinkerOf; + + const SINK_NAME: &'static str = MONGODB_SINK; + + fn is_sink_decouple(_desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + match user_specified { + // Set default sink decouple to false, because mongodb sink writer only ensure delivery on checkpoint barrier + SinkDecouple::Default | SinkDecouple::Disable => Ok(false), + SinkDecouple::Enable => Ok(true), + } + } + + async fn validate(&self) -> Result<()> { + if !self.is_append_only { + if self.pk_indices.is_empty() { + return Err(SinkError::Config(anyhow!( + "Primary key not defined for upsert mongodb sink (please define in `primary_key` field)"))); + } + + // checking if there is a non-pk field's name is `_id` + if self + .schema + .fields + .iter() + .enumerate() + .any(|(i, field)| !self.pk_indices.contains(&i) && field.name == MONGODB_PK_NAME) + { + return Err(SinkError::Config(anyhow!( + "_id field must be the sink's primary key, but a non primary key field name is _id", + ))); + } + + // assume the sink's pk is (a, b) and then the data written to mongodb will be + // { "_id": {"a": 1, "b": 2}, "a": 1, "b": 2, ... } + // you can see that the compound pk (a, b) is turned into an Object {"a": 1, "b": 2} + // and the each pk field is become as a field of the document + // but if the sink's pk is (_id, b) and the data will be: + // { "_id": {"_id": 1, "b": 2}, "b": 2, ... } + // in this case, the original _id field of the compound pk has been overridden + // we should consider this is a schema error + if self.pk_indices.len() > 1 + && self + .pk_indices + .iter() + .map(|&idx| self.schema.fields[idx].name.as_str()) + .any(|field| field == MONGODB_PK_NAME) + { + return Err(SinkError::Config(anyhow!( + "primary key fields must not contain a field named _id" + ))); + } + } + + if self.config.bulk_write_max_entries > MONGODB_BULK_WRITE_SIZE_LIMIT { + return Err(SinkError::Config(anyhow!( + "mongodb.bulk_write.max_entries {} exceeds the limit {}", + self.config.bulk_write_max_entries, + MONGODB_BULK_WRITE_SIZE_LIMIT + ))); + } + + if let Err(err) = self.config.common.collection_name.parse::() { + return Err(SinkError::Config(anyhow!(err).context(format!( + "invalid collection.name {}", + self.config.common.collection_name + )))); + } + + // checking reachability + let client = self.config.common.build_client().await?; + let client = ClientGuard::new(self.param.sink_name.clone(), client); + client + .database("admin") + .run_command(doc! {"hello":1}, None) + .await + .map_err(|err| { + SinkError::Mongodb(anyhow!(err).context("failed to send hello command to mongodb")) + })?; + + if self.config.drop_collection_name_field && self.config.collection_name_field.is_none() { + return Err(SinkError::Config(anyhow!( + "collection.name.field must be specified when collection.name.field.drop is enabled" + ))); + } + + // checking dynamic collection name settings + if let Some(coll_field) = &self.config.collection_name_field { + let fields = self.schema.fields(); + + let coll_field_index = fields + .iter() + .enumerate() + .find_map(|(index, field)| { + if &field.name == coll_field { + Some(index) + } else { + None + } + }) + .ok_or(SinkError::Config(anyhow!( + "collection.name.field {} not found", + coll_field + )))?; + + if fields[coll_field_index].data_type() != risingwave_common::types::DataType::Varchar { + return Err(SinkError::Config(anyhow!( + "the type of collection.name.field {} must be varchar", + coll_field + ))); + } + + if !self.is_append_only && self.pk_indices.iter().any(|idx| *idx == coll_field_index) { + return Err(SinkError::Config(anyhow!( + "collection.name.field {} must not be equal to the primary key field", + coll_field + ))); + } + } + + Ok(()) + } + + async fn new_log_sinker(&self, writer_param: SinkWriterParam) -> Result { + Ok(MongodbSinkWriter::new( + format!("{}-{}", writer_param.executor_id, self.param.sink_name), + self.config.clone(), + self.schema.clone(), + self.pk_indices.clone(), + self.is_append_only, + ) + .await? + .into_log_sinker(writer_param.sink_metrics)) + } +} + +pub struct MongodbSinkWriter { + pub config: MongodbConfig, + payload_writer: MongodbPayloadWriter, + is_append_only: bool, + // TODO switching to bulk write API when mongodb driver supports it + command_builder: CommandBuilder, +} + +impl MongodbSinkWriter { + pub async fn new( + name: String, + config: MongodbConfig, + schema: Schema, + pk_indices: Vec, + is_append_only: bool, + ) -> Result { + let client = config.common.build_client().await?; + + let default_namespace = + config + .common + .collection_name + .parse() + .map_err(|err: mongodb::error::Error| { + SinkError::Mongodb(anyhow!(err).context("parsing default namespace failed")) + })?; + + let coll_name_field_index = + config + .collection_name_field + .as_ref() + .and_then(|coll_name_field| { + schema + .names_str() + .iter() + .position(|&name| coll_name_field == name) + }); + + let col_indices = if let Some(coll_name_field_index) = coll_name_field_index + && config.drop_collection_name_field + { + (0..schema.fields.len()) + .filter(|idx| *idx != coll_name_field_index) + .collect_vec() + } else { + (0..schema.fields.len()).collect_vec() + }; + + let row_encoder = BsonEncoder::new(schema.clone(), Some(col_indices), pk_indices.clone()); + + let command_builder = if is_append_only { + CommandBuilder::AppendOnly(HashMap::new()) + } else { + CommandBuilder::Upsert(HashMap::new()) + }; + + let payload_writer = MongodbPayloadWriter::new( + schema, + pk_indices, + default_namespace, + coll_name_field_index, + ClientGuard::new(name, client), + config.bulk_write_max_entries, + row_encoder, + ); + + Ok(Self { + config, + payload_writer, + is_append_only, + command_builder, + }) + } + + async fn append(&mut self, chunk: StreamChunk) -> Result<()> { + let insert_builder = + must_match!(&mut self.command_builder, CommandBuilder::AppendOnly(builder) => builder); + for (op, row) in chunk.rows() { + if op != Op::Insert { + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + suppressed_count, + ?op, + ?row, + "non-insert op received in append-only mode" + ); + } + continue; + } + self.payload_writer.append(insert_builder, row).await?; + } + Ok(()) + } + + async fn upsert(&mut self, chunk: StreamChunk) -> Result<()> { + let upsert_builder = + must_match!(&mut self.command_builder, CommandBuilder::Upsert(builder) => builder); + for (op, row) in chunk.rows() { + if op == Op::UpdateDelete { + // we should ignore the `UpdateDelete` in upsert mode + continue; + } + self.payload_writer.upsert(upsert_builder, op, row).await?; + } + Ok(()) + } +} + +#[async_trait] +impl SinkWriter for MongodbSinkWriter { + async fn begin_epoch(&mut self, _epoch: u64) -> Result<()> { + Ok(()) + } + + async fn write_batch(&mut self, chunk: StreamChunk) -> Result<()> { + if self.is_append_only { + self.append(chunk).await + } else { + self.upsert(chunk).await + } + } + + async fn barrier(&mut self, is_checkpoint: bool) -> Result { + if is_checkpoint { + if self.is_append_only { + let insert_builder = must_match!(&mut self.command_builder, CommandBuilder::AppendOnly(builder) => builder); + self.payload_writer.flush_insert(insert_builder).await?; + } else { + let upsert_builder = must_match!(&mut self.command_builder, CommandBuilder::Upsert(builder) => builder); + self.payload_writer.flush_upsert(upsert_builder).await?; + } + } + Ok(()) + } +} + +struct InsertCommandBuilder { + coll: String, + inserts: Array, +} + +impl InsertCommandBuilder { + fn new(coll: String, capacity: usize) -> Self { + Self { + coll, + inserts: Array::with_capacity(capacity), + } + } + + fn append(&mut self, row: Document) { + self.inserts.push(Bson::Document(row)); + } + + fn build(self) -> Document { + doc! { + "insert": self.coll, + "ordered": true, + "documents": self.inserts, + } + } +} + +struct UpsertCommandBuilder { + coll: String, + updates: Array, + deletes: HashMap, Document>, +} + +impl UpsertCommandBuilder { + fn new(coll: String, capacity: usize) -> Self { + Self { + coll, + updates: Array::with_capacity(capacity), + deletes: HashMap::with_capacity(capacity), + } + } + + fn add_upsert(&mut self, pk: Document, row: Document) -> Result<()> { + let pk_data = mongodb::bson::to_vec(&pk).map_err(|err| { + SinkError::Mongodb(anyhow!(err).context("cannot serialize primary key")) + })?; + // under same pk, if the record currently being upserted was marked for deletion previously, we should + // revert the deletion, otherwise, the upserting record may be accidentally deleted. + // see https://github.com/risingwavelabs/risingwave/pull/17102#discussion_r1630684160 for more information. + self.deletes.remove(&pk_data); + + self.updates.push(bson!( { + "q": pk, + "u": row, + "upsert": true, + "multi": false, + })); + + Ok(()) + } + + fn add_delete(&mut self, pk: Document) -> Result<()> { + let pk_data = mongodb::bson::to_vec(&pk).map_err(|err| { + SinkError::Mongodb(anyhow!(err).context("cannot serialize primary key")) + })?; + self.deletes.insert(pk_data, pk); + Ok(()) + } + + fn build(self) -> (Option, Option) { + let (mut upsert_document, mut delete_document) = (None, None); + if !self.updates.is_empty() { + upsert_document = Some(doc! { + "update": self.coll.clone(), + "ordered": true, + "updates": self.updates, + }); + } + if !self.deletes.is_empty() { + let deletes = self + .deletes + .into_values() + .map(|pk| { + bson!({ + "q": pk, + "limit": 1, + }) + }) + .collect::(); + + delete_document = Some(doc! { + "delete": self.coll, + "ordered": true, + "deletes": deletes, + }); + } + (upsert_document, delete_document) + } +} + +enum CommandBuilder { + AppendOnly(HashMap), + Upsert(HashMap), +} + +type MongodbNamespace = (String, String); + +// In the future, we may build the payload into RawBSON to gain a better performance. +// The current API (mongodb-2.8.2) lacks the support of writing RawBSON. +struct MongodbPayloadWriter { + schema: Schema, + pk_indices: Vec, + default_namespace: Namespace, + coll_name_field_index: Option, + client: ClientGuard, + buffered_entries: usize, + max_entries: usize, + row_encoder: BsonEncoder, +} + +impl MongodbPayloadWriter { + fn new( + schema: Schema, + pk_indices: Vec, + default_namespace: Namespace, + coll_name_field_index: Option, + client: ClientGuard, + max_entries: usize, + row_encoder: BsonEncoder, + ) -> Self { + Self { + schema, + pk_indices, + default_namespace, + coll_name_field_index, + client, + buffered_entries: 0, + max_entries, + row_encoder, + } + } + + fn extract_namespace_from_row_ref(&self, row: RowRef<'_>) -> MongodbNamespace { + let ns = self.coll_name_field_index.and_then(|coll_name_field_index| { + match row.datum_at(coll_name_field_index) { + Some(ScalarRefImpl::Utf8(v)) => match v.parse::() { + Ok(ns) => Some(ns), + Err(err) => { + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + suppressed_count, + error = %err.as_report(), + collection_name = %v, + "parsing collection name failed, fallback to use default collection.name" + ); + } + None + } + }, + _ => { + if let Ok(suppressed_count) = LOG_SUPPERSSER.check() { + tracing::warn!( + suppressed_count, + "the value of collection.name.field is null, fallback to use default collection.name" + ); + } + None + } + } + }); + match ns { + Some(ns) => (ns.db, ns.coll), + None => ( + self.default_namespace.db.clone(), + self.default_namespace.coll.clone(), + ), + } + } + + async fn append( + &mut self, + insert_builder: &mut HashMap, + row: RowRef<'_>, + ) -> Result<()> { + let document = self.row_encoder.encode(row)?; + let ns = self.extract_namespace_from_row_ref(row); + let coll = ns.1.clone(); + + insert_builder + .entry(ns) + .or_insert_with(|| InsertCommandBuilder::new(coll, self.max_entries)) + .append(document); + + self.buffered_entries += 1; + if self.buffered_entries >= self.max_entries { + self.flush_insert(insert_builder).await?; + } + Ok(()) + } + + async fn upsert( + &mut self, + upsert_builder: &mut HashMap, + op: Op, + row: RowRef<'_>, + ) -> Result<()> { + let mut document = self.row_encoder.encode(row)?; + let ns = self.extract_namespace_from_row_ref(row); + let coll = ns.1.clone(); + + let pk = self.row_encoder.construct_pk(row); + + // Specify the primary key (_id) for the MongoDB collection if the user does not provide one. + if self.pk_indices.len() > 1 + || self.schema.fields[self.pk_indices[0]].name != MONGODB_PK_NAME + { + // compound pk should not have a field named `_id` + document.insert(MONGODB_PK_NAME, pk.clone()); + } + + let pk = doc! {MONGODB_PK_NAME: pk}; + match op { + Op::Insert | Op::UpdateInsert => upsert_builder + .entry(ns) + .or_insert_with(|| UpsertCommandBuilder::new(coll, self.max_entries)) + .add_upsert(pk, document)?, + Op::UpdateDelete => (), + Op::Delete => upsert_builder + .entry(ns) + .or_insert_with(|| UpsertCommandBuilder::new(coll, self.max_entries)) + .add_delete(pk)?, + } + + self.buffered_entries += 1; + if self.buffered_entries >= self.max_entries { + self.flush_upsert(upsert_builder).await?; + } + Ok(()) + } + + async fn flush_insert( + &mut self, + insert_builder: &mut HashMap, + ) -> Result<()> { + // TODO try sending bulk-write of each collection concurrently to improve the performance when + // `dynamic collection` is enabled. We may need to provide best practice to guide user on setting + // the MongoDB driver's connection properties. + for (ns, builder) in insert_builder.drain() { + self.send_bulk_write_command(&ns.0, builder.build()).await?; + } + self.buffered_entries = 0; + Ok(()) + } + + async fn flush_upsert( + &mut self, + upsert_builder: &mut HashMap, + ) -> Result<()> { + // TODO try sending bulk-write of each collection concurrently to improve the performance when + // `dynamic collection` is enabled. We may need to provide best practice to guide user on setting + // the MongoDB driver's connection properties. + for (ns, builder) in upsert_builder.drain() { + let (upsert, delete) = builder.build(); + // we are sending the bulk upsert first because, under same pk, the `Insert` and `UpdateInsert` + // should always appear before `Delete`. we have already ignored the `UpdateDelete` + // which is useless in upsert mode. + if let Some(upsert) = upsert { + self.send_bulk_write_command(&ns.0, upsert).await?; + } + if let Some(delete) = delete { + self.send_bulk_write_command(&ns.0, delete).await?; + } + } + self.buffered_entries = 0; + Ok(()) + } + + async fn send_bulk_write_command(&self, database: &str, command: Document) -> Result<()> { + let db = self.client.database(database); + + let result = db.run_command(command, None).await.map_err(|err| { + SinkError::Mongodb(anyhow!(err).context(format!( + "sending bulk write command failed, database: {}", + database + ))) + })?; + + if let Ok(write_errors) = result.get_array("writeErrors") { + return Err(SinkError::Mongodb(anyhow!( + "bulk write respond with write errors: {:?}", + write_errors, + ))); + } + + let n = result.get_i32("n").map_err(|err| { + SinkError::Mongodb( + anyhow!(err).context("can't extract field n from bulk write response"), + ) + })?; + if n < 1 { + return Err(SinkError::Mongodb(anyhow!( + "bulk write respond with an abnormal state, n = {}", + n + ))); + } + + Ok(()) + } +} diff --git a/src/connector/src/sink/mqtt.rs b/src/connector/src/sink/mqtt.rs index 308ec450878a9..9e86b5ec97e2a 100644 --- a/src/connector/src/sink/mqtt.rs +++ b/src/connector/src/sink/mqtt.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use core::fmt::Debug; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -120,6 +120,7 @@ pub struct MqttSink { pub struct MqttSinkWriter { pub config: MqttConfig, payload_writer: MqttSinkPayloadWriter, + #[expect(dead_code)] schema: Schema, encoder: RowEncoderWrapper, stopped: Arc, @@ -127,7 +128,7 @@ pub struct MqttSinkWriter { /// Basic data types for use with the mqtt interface impl MqttConfig { - pub fn from_hashmap(values: HashMap) -> Result { + pub fn from_btreemap(values: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(values).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; if config.r#type != SINK_TYPE_APPEND_ONLY { @@ -145,7 +146,7 @@ impl TryFrom for MqttSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = MqttConfig::from_hashmap(param.properties)?; + let config = MqttConfig::from_btreemap(param.properties)?; Ok(Self { config, schema, @@ -164,11 +165,10 @@ impl Sink for MqttSink { const SINK_NAME: &'static str = MQTT_SINK; - fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + fn is_sink_decouple(_desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { match user_specified { - SinkDecouple::Default => Ok(desc.sink_type.is_append_only()), + SinkDecouple::Default | SinkDecouple::Enable => Ok(true), SinkDecouple::Disable => Ok(false), - SinkDecouple::Enable => Ok(true), } } @@ -288,7 +288,7 @@ impl MqttSinkWriter { } err => { tracing::error!("Failed to poll mqtt eventloop: {}", err.as_report()); - std::thread::sleep(std::time::Duration::from_secs(1)); + tokio::time::sleep(std::time::Duration::from_secs(1)).await; } }, } diff --git a/src/connector/src/sink/nats.rs b/src/connector/src/sink/nats.rs index f4cf6ac4d7372..471ce61298417 100644 --- a/src/connector/src/sink/nats.rs +++ b/src/connector/src/sink/nats.rs @@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. use core::fmt::Debug; -use std::collections::HashMap; +use core::future::IntoFuture; +use std::collections::BTreeMap; use anyhow::{anyhow, Context as _}; use async_nats::jetstream::context::Context; +use futures::prelude::TryFuture; +use futures::FutureExt; use risingwave_common::array::StreamChunk; use risingwave_common::catalog::Schema; use risingwave_common::session_config::sink_decouple::SinkDecouple; @@ -38,6 +41,7 @@ use crate::sink::writer::{ use crate::sink::{Result, Sink, SinkError, SinkParam, SINK_TYPE_APPEND_ONLY}; pub const NATS_SINK: &str = "nats"; +const NATS_SEND_FUTURE_BUFFER_MAX_SIZE: usize = 65536; #[serde_as] #[derive(Clone, Debug, Deserialize, WithOptions)] @@ -59,13 +63,16 @@ pub struct NatsSink { pub struct NatsSinkWriter { pub config: NatsConfig, context: Context, + #[expect(dead_code)] schema: Schema, json_encoder: JsonEncoder, } +pub type NatsSinkDeliveryFuture = impl TryFuture + Unpin + 'static; + /// Basic data types for use with the nats interface impl NatsConfig { - pub fn from_hashmap(values: HashMap) -> Result { + pub fn from_btreemap(values: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(values).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; if config.r#type != SINK_TYPE_APPEND_ONLY { @@ -83,7 +90,7 @@ impl TryFrom for NatsSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = NatsConfig::from_hashmap(param.properties)?; + let config = NatsConfig::from_btreemap(param.properties)?; Ok(Self { config, schema, @@ -98,11 +105,10 @@ impl Sink for NatsSink { const SINK_NAME: &'static str = NATS_SINK; - fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + fn is_sink_decouple(_desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { match user_specified { - SinkDecouple::Default => Ok(desc.sink_type.is_append_only()), + SinkDecouple::Default | SinkDecouple::Enable => Ok(true), SinkDecouple::Disable => Ok(false), - SinkDecouple::Enable => Ok(true), } } @@ -122,7 +128,7 @@ impl Sink for NatsSink { Ok( NatsSinkWriter::new(self.config.clone(), self.schema.clone()) .await? - .into_log_sinker(usize::MAX), + .into_log_sinker(NATS_SEND_FUTURE_BUFFER_MAX_SIZE), ) } } @@ -148,34 +154,39 @@ impl NatsSinkWriter { ), }) } - - async fn append_only(&mut self, chunk: StreamChunk) -> Result<()> { - Retry::spawn( - ExponentialBackoff::from_millis(100).map(jitter).take(3), - || async { - let data = chunk_to_json(chunk.clone(), &self.json_encoder).unwrap(); - for item in data { - self.context - .publish(self.config.common.subject.clone(), item.into()) - .await - .context("nats sink error") - .map_err(SinkError::Nats)?; - } - Ok::<_, SinkError>(()) - }, - ) - .await - .context("nats sink error") - .map_err(SinkError::Nats) - } } impl AsyncTruncateSinkWriter for NatsSinkWriter { + type DeliveryFuture = NatsSinkDeliveryFuture; + async fn write_chunk<'a>( &'a mut self, chunk: StreamChunk, - _add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>, + mut add_future: DeliveryFutureManagerAddFuture<'a, Self::DeliveryFuture>, ) -> Result<()> { - self.append_only(chunk).await + let mut data = chunk_to_json(chunk, &self.json_encoder).unwrap(); + for item in &mut data { + let publish_ack_future = Retry::spawn( + ExponentialBackoff::from_millis(100).map(jitter).take(3), + || async { + self.context + .publish(self.config.common.subject.clone(), item.clone().into()) + .await + .context("nats sink error") + .map_err(SinkError::Nats) + }, + ) + .await + .context("nats sink error") + .map_err(SinkError::Nats)?; + let future = publish_ack_future.into_future().map(|result| { + result + .context("Nats sink error") + .map_err(SinkError::Nats) + .map(|_| ()) + }); + add_future.add_future_may_await(future).await?; + } + Ok(()) } } diff --git a/src/connector/src/sink/pulsar.rs b/src/connector/src/sink/pulsar.rs index 2ffb3e18fb39f..3f016ad94946d 100644 --- a/src/connector/src/sink/pulsar.rs +++ b/src/connector/src/sink/pulsar.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use std::fmt::Debug; use std::time::Duration; @@ -127,7 +127,7 @@ pub struct PulsarConfig { } impl PulsarConfig { - pub fn from_hashmap(values: HashMap) -> Result { + pub fn from_btreemap(values: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(values).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -150,7 +150,7 @@ impl TryFrom for PulsarSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = PulsarConfig::from_hashmap(param.properties)?; + let config = PulsarConfig::from_btreemap(param.properties)?; Ok(Self { config, schema, @@ -170,11 +170,10 @@ impl Sink for PulsarSink { const SINK_NAME: &'static str = PULSAR_SINK; - fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + fn is_sink_decouple(_desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { match user_specified { - SinkDecouple::Default => Ok(desc.sink_type.is_append_only()), + SinkDecouple::Default | SinkDecouple::Enable => Ok(true), SinkDecouple::Disable => Ok(false), - SinkDecouple::Enable => Ok(true), } } @@ -224,6 +223,7 @@ impl Sink for PulsarSink { pub struct PulsarSinkWriter { formatter: SinkFormatterImpl, + #[expect(dead_code)] pulsar: Pulsar, producer: Producer, config: PulsarConfig, @@ -286,7 +286,7 @@ impl<'w> PulsarPayloadWriter<'w> { if retry_num > 0 { tracing::warn!("Failed to send message, at retry no. {retry_num}"); } - match self.producer.send(message.clone()).await { + match self.producer.send_non_blocking(message.clone()).await { // If the message is sent successfully, // a SendFuture holding the message receipt // or error after sending is returned diff --git a/src/connector/src/sink/redis.rs b/src/connector/src/sink/redis.rs index 96b524d5b7b21..9d6a33d5131a4 100644 --- a/src/connector/src/sink/redis.rs +++ b/src/connector/src/sink/redis.rs @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use anyhow::anyhow; use async_trait::async_trait; -use redis::aio::{ConnectionLike, MultiplexedConnection}; -use redis::cluster::ClusterClient; -use redis::cluster_async::ClusterConnection; +use redis::aio::MultiplexedConnection; +use redis::cluster::{ClusterClient, ClusterConnection, ClusterPipeline}; use redis::{Client as RedisClient, Pipeline}; use risingwave_common::array::StreamChunk; use risingwave_common::catalog::Schema; @@ -49,46 +48,63 @@ pub struct RedisCommon { #[serde(rename = "redis.url")] pub url: String, } -pub enum RedisConn { - // Redis deployed as a cluster, clusters with only one node should also use this conn - Cluster(ClusterConnection), - // Redis is not deployed as a cluster - Single(MultiplexedConnection), + +pub enum RedisPipe { + Cluster(ClusterPipeline), + Single(Pipeline), } +impl RedisPipe { + pub async fn query( + &self, + conn: &mut RedisConn, + ) -> ConnectorResult { + match (self, conn) { + (RedisPipe::Cluster(pipe), RedisConn::Cluster(conn)) => Ok(pipe.query(conn)?), + (RedisPipe::Single(pipe), RedisConn::Single(conn)) => { + Ok(pipe.query_async(conn).await?) + } + _ => Err(SinkError::Redis("RedisPipe and RedisConn not match".to_string()).into()), + } + } -impl ConnectionLike for RedisConn { - fn req_packed_command<'a>( - &'a mut self, - cmd: &'a redis::Cmd, - ) -> redis::RedisFuture<'a, redis::Value> { + pub fn clear(&mut self) { match self { - RedisConn::Cluster(conn) => conn.req_packed_command(cmd), - RedisConn::Single(conn) => conn.req_packed_command(cmd), + RedisPipe::Cluster(pipe) => pipe.clear(), + RedisPipe::Single(pipe) => pipe.clear(), } } - fn req_packed_commands<'a>( - &'a mut self, - cmd: &'a redis::Pipeline, - offset: usize, - count: usize, - ) -> redis::RedisFuture<'a, Vec> { + pub fn set(&mut self, k: String, v: Vec) { match self { - RedisConn::Cluster(conn) => conn.req_packed_commands(cmd, offset, count), - RedisConn::Single(conn) => conn.req_packed_commands(cmd, offset, count), - } + RedisPipe::Cluster(pipe) => { + pipe.set(k, v); + } + RedisPipe::Single(pipe) => { + pipe.set(k, v); + } + }; } - fn get_db(&self) -> i64 { + pub fn del(&mut self, k: String) { match self { - RedisConn::Cluster(conn) => conn.get_db(), - RedisConn::Single(conn) => conn.get_db(), - } + RedisPipe::Cluster(pipe) => { + pipe.del(k); + } + RedisPipe::Single(pipe) => { + pipe.del(k); + } + }; } } +pub enum RedisConn { + // Redis deployed as a cluster, clusters with only one node should also use this conn + Cluster(ClusterConnection), + // Redis is not deployed as a cluster + Single(MultiplexedConnection), +} impl RedisCommon { - pub async fn build_conn(&self) -> ConnectorResult { + pub async fn build_conn_and_pipe(&self) -> ConnectorResult<(RedisConn, RedisPipe)> { match serde_json::from_str(&self.url).map_err(|e| SinkError::Config(anyhow!(e))) { Ok(v) => { if let Value::Array(list) = v { @@ -107,20 +123,25 @@ impl RedisCommon { .collect::>>()?; let client = ClusterClient::new(list)?; - Ok(RedisConn::Cluster(client.get_async_connection().await?)) + Ok(( + RedisConn::Cluster(client.get_connection()?), + RedisPipe::Cluster(redis::cluster::cluster_pipe()), + )) } else { Err(SinkError::Redis("redis.url must be array or string".to_string()).into()) } } Err(_) => { let client = RedisClient::open(self.url.clone())?; - Ok(RedisConn::Single( - client.get_multiplexed_async_connection().await?, + Ok(( + RedisConn::Single(client.get_multiplexed_async_connection().await?), + RedisPipe::Single(redis::pipe()), )) } } } } + #[serde_as] #[derive(Clone, Debug, Deserialize, WithOptions)] pub struct RedisConfig { @@ -129,7 +150,7 @@ pub struct RedisConfig { } impl RedisConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(properties).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -157,7 +178,7 @@ impl TryFrom for RedisSink { "Redis Sink Primary Key must be specified." ))); } - let config = RedisConfig::from_hashmap(param.properties.clone())?; + let config = RedisConfig::from_btreemap(param.properties.clone())?; Ok(Self { config, schema: param.schema(), @@ -191,7 +212,7 @@ impl Sink for RedisSink { } async fn validate(&self) -> Result<()> { - let _conn = self.config.common.build_conn().await?; + self.config.common.build_conn_and_pipe().await?; let all_set: HashSet = self .schema .fields() @@ -228,8 +249,11 @@ impl Sink for RedisSink { } pub struct RedisSinkWriter { + #[expect(dead_code)] epoch: u64, + #[expect(dead_code)] schema: Schema, + #[expect(dead_code)] pk_indices: Vec, formatter: SinkFormatterImpl, payload_writer: RedisSinkPayloadWriter, @@ -239,13 +263,13 @@ struct RedisSinkPayloadWriter { // connection to redis, one per executor conn: Option, // the command pipeline for write-commit - pipe: Pipeline, + pipe: RedisPipe, } + impl RedisSinkPayloadWriter { pub async fn new(config: RedisConfig) -> Result { - let conn = config.common.build_conn().await?; + let (conn, pipe) = config.common.build_conn_and_pipe().await?; let conn = Some(conn); - let pipe = redis::pipe(); Ok(Self { conn, pipe }) } @@ -253,7 +277,7 @@ impl RedisSinkPayloadWriter { #[cfg(test)] pub fn mock() -> Self { let conn = None; - let pipe = redis::pipe(); + let pipe = RedisPipe::Single(redis::pipe()); Self { conn, pipe } } @@ -264,7 +288,7 @@ impl RedisSinkPayloadWriter { return Ok(()); } } - self.pipe.query_async(self.conn.as_mut().unwrap()).await?; + self.pipe.query(self.conn.as_mut().unwrap()).await?; self.pipe.clear(); Ok(()) } @@ -353,11 +377,11 @@ impl AsyncTruncateSinkWriter for RedisSinkWriter { #[cfg(test)] mod test { - use std::collections::BTreeMap; + use core::panic; use rdkafka::message::FromBytes; - use risingwave_common::array::{Array, I32Array, Op, StreamChunk, Utf8Array}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::{Array, I32Array, Op, Utf8Array}; + use risingwave_common::catalog::Field; use risingwave_common::types::DataType; use risingwave_common::util::iter_util::ZipEqDebug; @@ -386,6 +410,7 @@ mod test { format: SinkFormat::AppendOnly, encode: SinkEncode::Json, options: BTreeMap::default(), + key_encode: None, }; let mut redis_sink_writer = RedisSinkWriter::mock(schema, vec![0], &format_desc) @@ -406,24 +431,33 @@ mod test { .write_chunk(chunk_a, manager.start_write_chunk(0, 0)) .await .expect("failed to write batch"); - let expected_a = - vec![ - (0, "*3\r\n$3\r\nSET\r\n$8\r\n{\"id\":1}\r\n$23\r\n{\"id\":1,\"name\":\"Alice\"}\r\n"), - (1, "*3\r\n$3\r\nSET\r\n$8\r\n{\"id\":2}\r\n$21\r\n{\"id\":2,\"name\":\"Bob\"}\r\n"), - (2, "*3\r\n$3\r\nSET\r\n$8\r\n{\"id\":3}\r\n$23\r\n{\"id\":3,\"name\":\"Clare\"}\r\n"), + let expected_a = vec![ + ( + 0, + "*3\r\n$3\r\nSET\r\n$8\r\n{\"id\":1}\r\n$23\r\n{\"id\":1,\"name\":\"Alice\"}\r\n", + ), + ( + 1, + "*3\r\n$3\r\nSET\r\n$8\r\n{\"id\":2}\r\n$21\r\n{\"id\":2,\"name\":\"Bob\"}\r\n", + ), + ( + 2, + "*3\r\n$3\r\nSET\r\n$8\r\n{\"id\":3}\r\n$23\r\n{\"id\":3,\"name\":\"Clare\"}\r\n", + ), ]; - redis_sink_writer - .payload_writer - .pipe - .cmd_iter() - .enumerate() - .zip_eq_debug(expected_a.clone()) - .for_each(|((i, cmd), (exp_i, exp_cmd))| { - if exp_i == i { - assert_eq!(exp_cmd, str::from_bytes(&cmd.get_packed_command()).unwrap()) - } - }); + if let RedisPipe::Single(pipe) = &redis_sink_writer.payload_writer.pipe { + pipe.cmd_iter() + .enumerate() + .zip_eq_debug(expected_a.clone()) + .for_each(|((i, cmd), (exp_i, exp_cmd))| { + if exp_i == i { + assert_eq!(exp_cmd, str::from_bytes(&cmd.get_packed_command()).unwrap()) + } + }); + } else { + panic!("pipe type not match") + } } #[tokio::test] @@ -453,6 +487,7 @@ mod test { format: SinkFormat::AppendOnly, encode: SinkEncode::Template, options: btree_map, + key_encode: None, }; let mut redis_sink_writer = RedisSinkWriter::mock(schema, vec![0], &format_desc) @@ -488,16 +523,17 @@ mod test { ), ]; - redis_sink_writer - .payload_writer - .pipe - .cmd_iter() - .enumerate() - .zip_eq_debug(expected_a.clone()) - .for_each(|((i, cmd), (exp_i, exp_cmd))| { - if exp_i == i { - assert_eq!(exp_cmd, str::from_bytes(&cmd.get_packed_command()).unwrap()) - } - }); + if let RedisPipe::Single(pipe) = &redis_sink_writer.payload_writer.pipe { + pipe.cmd_iter() + .enumerate() + .zip_eq_debug(expected_a.clone()) + .for_each(|((i, cmd), (exp_i, exp_cmd))| { + if exp_i == i { + assert_eq!(exp_cmd, str::from_bytes(&cmd.get_packed_command()).unwrap()) + } + }); + } else { + panic!("pipe type not match") + }; } } diff --git a/src/connector/src/sink/remote.rs b/src/connector/src/sink/remote.rs index 4d3c48b6ec5c7..d0edded78d6db 100644 --- a/src/connector/src/sink/remote.rs +++ b/src/connector/src/sink/remote.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{HashMap, VecDeque}; +use std::collections::{BTreeMap, VecDeque}; use std::marker::PhantomData; use std::ops::Deref; use std::pin::pin; @@ -20,8 +20,9 @@ use std::time::Instant; use anyhow::{anyhow, Context}; use async_trait::async_trait; +use await_tree::InstrumentAwait; use futures::future::select; -use futures::{StreamExt, TryStreamExt}; +use futures::TryStreamExt; use itertools::Itertools; use jni::JavaVM; use prost::Message; @@ -30,7 +31,7 @@ use risingwave_common::bail; use risingwave_common::catalog::{ColumnDesc, ColumnId}; use risingwave_common::session_config::sink_decouple::SinkDecouple; use risingwave_common::types::DataType; -use risingwave_jni_core::jvm_runtime::JVM; +use risingwave_jni_core::jvm_runtime::{execute_with_jni_env, JVM}; use risingwave_jni_core::{ call_static_method, gen_class_name, JniReceiverType, JniSenderType, JniSinkWriterStreamRequest, }; @@ -41,8 +42,8 @@ use risingwave_pb::connector_service::sink_writer_stream_request::{ use risingwave_pb::connector_service::{ sink_coordinator_stream_request, sink_coordinator_stream_response, sink_writer_stream_response, PbSinkParam, SinkCoordinatorStreamRequest, SinkCoordinatorStreamResponse, SinkMetadata, - SinkPayloadFormat, SinkWriterStreamRequest, SinkWriterStreamResponse, TableSchema, - ValidateSinkRequest, ValidateSinkResponse, + SinkWriterStreamRequest, SinkWriterStreamResponse, TableSchema, ValidateSinkRequest, + ValidateSinkResponse, }; use risingwave_rpc_client::error::RpcError; use risingwave_rpc_client::{ @@ -52,12 +53,12 @@ use risingwave_rpc_client::{ use rw_futures_util::drop_either_future; use thiserror_ext::AsReport; use tokio::sync::mpsc; -use tokio::sync::mpsc::{unbounded_channel, Receiver, Sender}; +use tokio::sync::mpsc::{unbounded_channel, Receiver}; use tokio::task::spawn_blocking; use tokio_stream::wrappers::ReceiverStream; use tracing::warn; -use super::elasticsearch::{StreamChunkConverter, ES_OPTION_DELIMITER}; +use super::elasticsearch::{is_es_sink, StreamChunkConverter, ES_OPTION_DELIMITER}; use crate::error::ConnectorResult; use crate::sink::catalog::desc::SinkDesc; use crate::sink::coordinate::CoordinatedSinkWriter; @@ -67,12 +68,12 @@ use crate::sink::{ DummySinkCommitCoordinator, LogSinker, Result, Sink, SinkCommitCoordinator, SinkError, SinkLogReader, SinkMetrics, SinkParam, SinkWriterParam, }; -use crate::ConnectorParams; macro_rules! def_remote_sink { () => { def_remote_sink! { { ElasticSearch, ElasticSearchSink, "elasticsearch" } + { Opensearch, OpensearchSink, "opensearch"} { Cassandra, CassandraSink, "cassandra" } { Jdbc, JdbcSink, "jdbc", |desc| { desc.sink_type.is_append_only() @@ -81,7 +82,6 @@ macro_rules! def_remote_sink { { HttpJava, HttpJavaSink, "http" } } }; - () => {}; ({ $variant_name:ident, $sink_type_name:ident, $sink_name:expr }) => { #[derive(Debug)] pub struct $variant_name; @@ -165,9 +165,9 @@ impl Sink for RemoteSink { } async fn validate_remote_sink(param: &SinkParam, sink_name: &str) -> ConnectorResult<()> { - if sink_name == ElasticSearchSink::SINK_NAME + if is_es_sink(sink_name) && param.downstream_pk.len() > 1 - && param.properties.get(ES_OPTION_DELIMITER).is_none() + && !param.properties.contains_key(ES_OPTION_DELIMITER) { bail!("Es sink only support single pk or pk with delimiter option"); } @@ -190,7 +190,7 @@ async fn validate_remote_sink(param: &SinkParam, sink_name: &str) -> ConnectorRe | DataType::Jsonb | DataType::Bytea => Ok(()), DataType::List(list) => { - if (sink_name==ElasticSearchSink::SINK_NAME) | matches!(list.as_ref(), DataType::Int16 | DataType::Int32 | DataType::Int64 | DataType::Float32 | DataType::Float64 | DataType::Varchar){ + if is_es_sink(sink_name) || matches!(list.as_ref(), DataType::Int16 | DataType::Int32 | DataType::Int64 | DataType::Float32 | DataType::Float64 | DataType::Varchar){ Ok(()) } else{ Err(SinkError::Remote(anyhow!( @@ -201,7 +201,7 @@ async fn validate_remote_sink(param: &SinkParam, sink_name: &str) -> ConnectorRe } }, DataType::Struct(_) => { - if sink_name==ElasticSearchSink::SINK_NAME{ + if is_es_sink(sink_name){ Ok(()) }else{ Err(SinkError::Remote(anyhow!( @@ -221,28 +221,29 @@ async fn validate_remote_sink(param: &SinkParam, sink_name: &str) -> ConnectorRe let sink_param = param.to_proto(); spawn_blocking(move || -> anyhow::Result<()> { - let mut env = jvm.attach_current_thread()?; - let validate_sink_request = ValidateSinkRequest { - sink_param: Some(sink_param), - }; - let validate_sink_request_bytes = - env.byte_array_from_slice(&Message::encode_to_vec(&validate_sink_request))?; - - let validate_sink_response_bytes = call_static_method!( - env, - {com.risingwave.connector.JniSinkValidationHandler}, - {byte[] validate(byte[] validateSourceRequestBytes)}, - &validate_sink_request_bytes - )?; - - let validate_sink_response: ValidateSinkResponse = Message::decode( - risingwave_jni_core::to_guarded_slice(&validate_sink_response_bytes, &mut env)?.deref(), - )?; - - validate_sink_response.error.map_or_else( - || Ok(()), // If there is no error message, return Ok here. - |err| bail!("sink cannot pass validation: {}", err.error_message), - ) + execute_with_jni_env(jvm, |env| { + let validate_sink_request = ValidateSinkRequest { + sink_param: Some(sink_param), + }; + let validate_sink_request_bytes = + env.byte_array_from_slice(&Message::encode_to_vec(&validate_sink_request))?; + + let validate_sink_response_bytes = call_static_method!( + env, + {com.risingwave.connector.JniSinkValidationHandler}, + {byte[] validate(byte[] validateSourceRequestBytes)}, + &validate_sink_request_bytes + )?; + + let validate_sink_response: ValidateSinkResponse = Message::decode( + risingwave_jni_core::to_guarded_slice(&validate_sink_response_bytes, env)?.deref(), + )?; + + validate_sink_response.error.map_or_else( + || Ok(()), // If there is no error message, return Ok here. + |err| bail!("sink cannot pass validation: {}", err.error_message), + ) + }) }) .await .context("JoinHandle returns error")??; @@ -264,7 +265,7 @@ impl RemoteLogSinker { sink_name: &str, ) -> Result { let sink_proto = sink_param.to_proto(); - let payload_schema = if sink_name == ElasticSearchSink::SINK_NAME { + let payload_schema = if is_es_sink(sink_name) { let columns = vec![ ColumnDesc::unnamed(ColumnId::from(0), DataType::Varchar).to_protobuf(), ColumnDesc::unnamed(ColumnId::from(1), DataType::Varchar).to_protobuf(), @@ -282,7 +283,7 @@ impl RemoteLogSinker { request_sender, response_stream, } = EmbeddedConnectorClient::new()? - .start_sink_writer_stream(payload_schema, sink_proto, SinkPayloadFormat::StreamChunk) + .start_sink_writer_stream(payload_schema, sink_proto) .await?; let sink_metrics = writer_param.sink_metrics; @@ -302,7 +303,7 @@ impl RemoteLogSinker { #[async_trait] impl LogSinker for RemoteLogSinker { - async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result<()> { + async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result { let mut request_tx = self.request_sender; let mut response_err_stream_rx = self.response_stream; let sink_metrics = self.sink_metrics; @@ -311,7 +312,11 @@ impl LogSinker for RemoteLogSinker { let poll_response_stream = async move { loop { - let result = response_err_stream_rx.stream.try_next().await; + let result = response_err_stream_rx + .stream + .try_next() + .instrument_await("log_sinker_wait_next_response") + .await; match result { Ok(Some(response)) => { response_tx.send(response).map_err(|err| { @@ -325,7 +330,7 @@ impl LogSinker for RemoteLogSinker { }; let poll_consume_log_and_sink = async move { - async fn truncate_matched_offset( + fn truncate_matched_offset( queue: &mut VecDeque<(TruncateOffset, Option)>, persisted_offset: TruncateOffset, log_reader: &mut impl SinkLogReader, @@ -356,7 +361,7 @@ impl LogSinker for RemoteLogSinker { .observe(start_time.elapsed().as_millis() as f64); } - log_reader.truncate(persisted_offset).await?; + log_reader.truncate(persisted_offset)?; Ok(()) } @@ -393,8 +398,7 @@ impl LogSinker for RemoteLogSinker { }, log_reader, &sink_metrics, - ) - .await?; + )?; } SinkWriterStreamResponse { response: @@ -413,8 +417,7 @@ impl LogSinker for RemoteLogSinker { TruncateOffset::Barrier { epoch }, log_reader, &sink_metrics, - ) - .await?; + )?; } response => { return Err(SinkError::Remote(anyhow!( @@ -445,6 +448,9 @@ impl LogSinker for RemoteLogSinker { batch_id: chunk_id as u64, chunk, }) + .instrument_await(format!( + "log_sinker_send_chunk (chunk {chunk_id})" + )) .await?; prev_offset = Some(offset); sent_offset_queue @@ -457,10 +463,20 @@ impl LogSinker for RemoteLogSinker { } let start_time = if is_checkpoint { let start_time = Instant::now(); - request_tx.barrier(epoch, true).await?; + request_tx + .barrier(epoch, true) + .instrument_await(format!( + "log_sinker_commit_checkpoint (epoch {epoch})" + )) + .await?; Some(start_time) } else { - request_tx.barrier(epoch, false).await?; + request_tx + .barrier(epoch, false) + .instrument_await(format!( + "log_sinker_send_barrier (epoch {epoch})" + )) + .await?; None }; prev_offset = Some(offset); @@ -522,12 +538,8 @@ impl Sink for CoordinatedRemoteSink { "sink needs coordination should not have singleton input" )) })?, - CoordinatedRemoteSinkWriter::new( - self.param.clone(), - writer_param.connector_params, - writer_param.sink_metrics.clone(), - ) - .await?, + CoordinatedRemoteSinkWriter::new(self.param.clone(), writer_param.sink_metrics.clone()) + .await?, ) .await? .into_log_sinker(writer_param.sink_metrics)) @@ -539,7 +551,8 @@ impl Sink for CoordinatedRemoteSink { } pub struct CoordinatedRemoteSinkWriter { - properties: HashMap, + #[expect(dead_code)] + properties: BTreeMap, epoch: Option, batch_id: u64, stream_handle: SinkWriterStreamHandle, @@ -547,18 +560,10 @@ pub struct CoordinatedRemoteSinkWriter { } impl CoordinatedRemoteSinkWriter { - pub async fn new( - param: SinkParam, - connector_params: ConnectorParams, - sink_metrics: SinkMetrics, - ) -> Result { + pub async fn new(param: SinkParam, sink_metrics: SinkMetrics) -> Result { let sink_proto = param.to_proto(); let stream_handle = EmbeddedConnectorClient::new()? - .start_sink_writer_stream( - sink_proto.table_schema.clone(), - sink_proto, - connector_params.sink_payload_format, - ) + .start_sink_writer_stream(sink_proto.table_schema.clone(), sink_proto) .await?; Ok(Self { @@ -570,11 +575,14 @@ impl CoordinatedRemoteSinkWriter { }) } + #[cfg(test)] fn for_test( response_receiver: Receiver>, - request_sender: Sender, + request_sender: tokio::sync::mpsc::Sender, ) -> CoordinatedRemoteSinkWriter { - let properties = HashMap::from([("output.path".to_string(), "/tmp/rw".to_string())]); + use futures::StreamExt; + + let properties = BTreeMap::from([("output.path".to_string(), "/tmp/rw".to_string())]); let stream_handle = SinkWriterStreamHandle::for_test( request_sender, @@ -655,11 +663,7 @@ impl RemoteCoordinator { .start_sink_coordinator_stream(param.clone()) .await?; - tracing::trace!( - "{:?} RemoteCoordinator started with properties: {:?}", - R::SINK_NAME, - ¶m.properties - ); + tracing::trace!("{:?} RemoteCoordinator started", R::SINK_NAME,); Ok(RemoteCoordinator { stream_handle }) } @@ -692,13 +696,11 @@ impl EmbeddedConnectorClient { &self, payload_schema: Option, sink_proto: PbSinkParam, - sink_payload_format: SinkPayloadFormat, ) -> Result> { let (handle, first_rsp) = SinkWriterStreamHandle::initialize( SinkWriterStreamRequest { request: Some(SinkRequest::Start(StartSink { sink_param: Some(sink_proto), - format: sink_payload_format as i32, payload_schema, })), }, @@ -769,34 +771,31 @@ impl EmbeddedConnectorClient { let jvm = self.jvm; std::thread::spawn(move || { - let mut env = match jvm - .attach_current_thread() - .context("failed to attach current thread") - { - Ok(env) => env, - Err(e) => { - let _ = response_tx.blocking_send(Err(e)); - return; - } - }; + let result = execute_with_jni_env(jvm, |env| { + let result = call_static_method!( + env, + class_name, + method_name, + {{void}, {long requestRx, long responseTx}}, + &mut request_rx as *mut JniReceiverType, + &mut response_tx as *mut JniSenderType + ); - let result = call_static_method!( - env, - class_name, - method_name, - {{void}, {long requestRx, long responseTx}}, - &mut request_rx as *mut JniReceiverType, - &mut response_tx as *mut JniSenderType - ); - - match result { - Ok(_) => { - tracing::info!("end of jni call {}::{}", class_name, method_name); - } - Err(e) => { - tracing::error!(error = %e.as_report(), "jni call error"); - } - }; + match result { + Ok(_) => { + tracing::info!("end of jni call {}::{}", class_name, method_name); + } + Err(e) => { + tracing::error!(error = %e.as_report(), "jni call error"); + } + }; + + Ok(()) + }); + + if let Err(e) = result { + let _ = response_tx.blocking_send(Err(e)); + } }); response_rx } diff --git a/src/connector/src/sink/snowflake.rs b/src/connector/src/sink/snowflake.rs index e4dbbfa59f17b..0f512933cd0f7 100644 --- a/src/connector/src/sink/snowflake.rs +++ b/src/connector/src/sink/snowflake.rs @@ -12,17 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt::Write; use std::sync::Arc; -use anyhow::anyhow; +use anyhow::{anyhow, Context}; use async_trait::async_trait; use bytes::{Bytes, BytesMut}; use risingwave_common::array::{Op, StreamChunk}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::Schema; -use risingwave_object_store::object::{ObjectStore, StreamingUploader}; +use risingwave_object_store::object::{ObjectStore, OpendalStreamingUploader, StreamingUploader}; use serde::Deserialize; use serde_json::Value; use serde_with::serde_as; @@ -39,7 +39,6 @@ use crate::sink::writer::SinkWriterExt; use crate::sink::{DummySinkCommitCoordinator, Result, Sink, SinkWriter, SinkWriterParam}; pub const SNOWFLAKE_SINK: &str = "snowflake"; -const INITIAL_ROW_CAPACITY: usize = 1024; #[derive(Deserialize, Debug, Clone, WithOptions)] pub struct SnowflakeCommon { @@ -108,7 +107,7 @@ pub struct SnowflakeConfig { } impl SnowflakeConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(properties).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -155,7 +154,7 @@ impl TryFrom for SnowflakeSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = SnowflakeConfig::from_hashmap(param.properties)?; + let config = SnowflakeConfig::from_btreemap(param.properties)?; Ok(SnowflakeSink { config, schema, @@ -166,9 +165,13 @@ impl TryFrom for SnowflakeSink { } pub struct SnowflakeSinkWriter { + #[expect(dead_code)] config: SnowflakeConfig, + #[expect(dead_code)] schema: Schema, + #[expect(dead_code)] pk_indices: Vec, + #[expect(dead_code)] is_append_only: bool, /// the client used to send `insertFiles` post request http_client: SnowflakeHttpClient, @@ -184,7 +187,7 @@ pub struct SnowflakeSinkWriter { /// note: the option here *implicitly* indicates whether we have at /// least call `streaming_upload` once during this epoch, /// which is mainly used to prevent uploading empty data. - streaming_uploader: Option<(Box, String)>, + streaming_uploader: Option<(OpendalStreamingUploader, String)>, } impl SnowflakeSinkWriter { @@ -242,7 +245,7 @@ impl SnowflakeSinkWriter { /// and `streaming_upload` being called the first time. /// i.e., lazily initialization of the internal `streaming_uploader`. /// plus, this function is *pure*, the `&mut self` here is to make rustc (and tokio) happy. - async fn new_streaming_uploader(&mut self) -> Result<(Box, String)> { + async fn new_streaming_uploader(&mut self) -> Result<(OpendalStreamingUploader, String)> { let file_suffix = self.file_suffix(); let path = generate_s3_file_name(self.s3_client.s3_path(), &file_suffix); let uploader = self @@ -250,13 +253,13 @@ impl SnowflakeSinkWriter { .opendal_s3_engine .streaming_upload(&path) .await - .map_err(|err| { - SinkError::Snowflake(format!( - "failed to create the streaming uploader of opendal s3 engine for epoch {}, error: {}", - self.epoch, - err - )) - })?; + .with_context(|| { + format!( + "failed to create the streaming uploader of opendal s3 engine for epoch {}", + self.epoch + ) + }) + .map_err(SinkError::Snowflake)?; Ok((uploader, file_suffix)) } @@ -276,12 +279,8 @@ impl SnowflakeSinkWriter { uploader .write_bytes(data) .await - .map_err(|err| { - SinkError::Snowflake(format!( - "failed to write bytes when streaming uploading to s3 for snowflake sink, error: {}", - err - )) - })?; + .context("failed to write bytes when streaming uploading to s3") + .map_err(SinkError::Snowflake)?; Ok(()) } @@ -293,12 +292,11 @@ impl SnowflakeSinkWriter { // there is no data to be uploaded for this epoch return Ok(None); }; - uploader.finish().await.map_err(|err| { - SinkError::Snowflake(format!( - "failed to finish streaming upload to s3 for snowflake sink, error: {}", - err - )) - })?; + uploader + .finish() + .await + .context("failed to finish streaming upload to s3") + .map_err(SinkError::Snowflake)?; Ok(Some(file_suffix)) } @@ -315,12 +313,7 @@ impl SnowflakeSinkWriter { "{}", Value::Object(self.row_encoder.encode(row)?) ) - .map_err(|err| { - SinkError::Snowflake(format!( - "failed to write json object to `row_buf`, error: {}", - err - )) - })?; + .unwrap(); // write to a `BytesMut` should never fail } // streaming upload in a chunk-by-chunk manner diff --git a/src/connector/src/sink/snowflake_connector.rs b/src/connector/src/sink/snowflake_connector.rs index 432d222a8b426..bfd2458900294 100644 --- a/src/connector/src/sink/snowflake_connector.rs +++ b/src/connector/src/sink/snowflake_connector.rs @@ -13,14 +13,15 @@ // limitations under the License. use std::collections::HashMap; +use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +use anyhow::{anyhow, Context}; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; +use object_metrics::GLOBAL_OBJECT_STORE_METRICS; use reqwest::{header, Client, RequestBuilder, StatusCode}; -use risingwave_common::config::ObjectStoreConfig; use risingwave_object_store::object::*; use serde::{Deserialize, Serialize}; -use thiserror_ext::AsReport; use super::doris_starrocks_connector::POOL_IDLE_TIMEOUT; use super::{Result, SinkError}; @@ -55,6 +56,7 @@ pub struct SnowflakeHttpClient { account: String, user: String, private_key: String, + #[expect(dead_code)] header: HashMap, s3_path: Option, } @@ -126,16 +128,12 @@ impl SnowflakeHttpClient { let jwt_token = encode( &header, &claims, - &EncodingKey::from_rsa_pem(self.private_key.as_ref()).map_err(|err| { - SinkError::Snowflake(format!( - "failed to encode from provided rsa pem key, error: {}", - err - )) - })?, + &EncodingKey::from_rsa_pem(self.private_key.as_ref()) + .context("failed to encode from provided rsa pem key") + .map_err(SinkError::Snowflake)?, ) - .map_err(|err| { - SinkError::Snowflake(format!("failed to encode jwt_token, error: {}", err)) - })?; + .context("failed to encode jwt_token") + .map_err(SinkError::Snowflake)?; Ok(jwt_token) } @@ -167,10 +165,10 @@ impl SnowflakeHttpClient { let response = builder .send() .await - .map_err(|err| SinkError::Snowflake(err.to_report_string()))?; + .map_err(|err| SinkError::Snowflake(anyhow!(err)))?; if response.status() != StatusCode::OK { - return Err(SinkError::Snowflake(format!( + return Err(SinkError::Snowflake(anyhow!( "failed to make http request, error code: {}\ndetailed response: {:#?}", response.status(), response, @@ -183,6 +181,7 @@ impl SnowflakeHttpClient { /// todo: refactor this part after s3 sink is available pub struct SnowflakeS3Client { + #[expect(dead_code)] s3_bucket: String, s3_path: Option, pub opendal_s3_engine: OpendalObjectStore, @@ -196,23 +195,22 @@ impl SnowflakeS3Client { aws_secret_access_key: String, aws_region: String, ) -> Result { + // FIXME: we should use the `ObjectStoreConfig` instead of default // just use default configuration here for opendal s3 engine let config = ObjectStoreConfig::default(); + let metrics = Arc::new(GLOBAL_OBJECT_STORE_METRICS.clone()); // create the s3 engine for streaming upload to the intermediate s3 bucket let opendal_s3_engine = OpendalObjectStore::new_s3_engine_with_credentials( &s3_bucket, - config, + Arc::new(config), + metrics, &aws_access_key_id, &aws_secret_access_key, &aws_region, ) - .map_err(|err| { - SinkError::Snowflake(format!( - "failed to create opendal s3 engine, error: {}", - err - )) - })?; + .context("failed to create opendal s3 engine") + .map_err(SinkError::Snowflake)?; Ok(Self { s3_bucket, diff --git a/src/connector/src/sink/sqlserver.rs b/src/connector/src/sink/sqlserver.rs new file mode 100644 index 0000000000000..959513e38b349 --- /dev/null +++ b/src/connector/src/sink/sqlserver.rs @@ -0,0 +1,649 @@ +// Copyright 2024 RisingWave Labs +// +// 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::{BTreeMap, HashMap}; +use std::sync::Arc; + +use anyhow::anyhow; +use async_trait::async_trait; +use risingwave_common::array::{Op, RowRef, StreamChunk}; +use risingwave_common::bitmap::Bitmap; +use risingwave_common::catalog::Schema; +use risingwave_common::row::{OwnedRow, Row}; +use risingwave_common::types::{DataType, Decimal}; +use serde_derive::Deserialize; +use serde_with::{serde_as, DisplayFromStr}; +use simd_json::prelude::ArrayTrait; +use tiberius::numeric::Numeric; +use tiberius::{AuthMethod, Client, ColumnData, Config, Query}; +use tokio::net::TcpStream; +use tokio_util::compat::TokioAsyncWriteCompatExt; +use with_options::WithOptions; + +use super::{SinkError, SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, SINK_TYPE_UPSERT}; +use crate::sink::writer::{LogSinkerOf, SinkWriter, SinkWriterExt}; +use crate::sink::{DummySinkCommitCoordinator, Result, Sink, SinkParam, SinkWriterParam}; + +pub const SQLSERVER_SINK: &str = "sqlserver"; + +fn default_max_batch_rows() -> usize { + 1024 +} + +#[serde_as] +#[derive(Clone, Debug, Deserialize, WithOptions)] +pub struct SqlServerConfig { + #[serde(rename = "sqlserver.host")] + pub host: String, + #[serde(rename = "sqlserver.port")] + #[serde_as(as = "DisplayFromStr")] + pub port: u16, + #[serde(rename = "sqlserver.user")] + pub user: String, + #[serde(rename = "sqlserver.password")] + pub password: String, + #[serde(rename = "sqlserver.database")] + pub database: String, + #[serde(rename = "sqlserver.table")] + pub table: String, + #[serde( + rename = "sqlserver.max_batch_rows", + default = "default_max_batch_rows" + )] + #[serde_as(as = "DisplayFromStr")] + pub max_batch_rows: usize, + pub r#type: String, // accept "append-only" or "upsert" +} + +impl SqlServerConfig { + pub fn from_btreemap(properties: BTreeMap) -> Result { + let config = + serde_json::from_value::(serde_json::to_value(properties).unwrap()) + .map_err(|e| SinkError::Config(anyhow!(e)))?; + if config.r#type != SINK_TYPE_APPEND_ONLY && config.r#type != SINK_TYPE_UPSERT { + return Err(SinkError::Config(anyhow!( + "`{}` must be {}, or {}", + SINK_TYPE_OPTION, + SINK_TYPE_APPEND_ONLY, + SINK_TYPE_UPSERT + ))); + } + Ok(config) + } +} + +#[derive(Debug)] +pub struct SqlServerSink { + pub config: SqlServerConfig, + schema: Schema, + pk_indices: Vec, + is_append_only: bool, +} + +impl SqlServerSink { + pub fn new( + mut config: SqlServerConfig, + schema: Schema, + pk_indices: Vec, + is_append_only: bool, + ) -> Result { + // Rewrite config because tiberius allows a maximum of 2100 params in one query request. + const TIBERIUS_PARAM_MAX: usize = 2000; + let params_per_op = schema.fields().len(); + let tiberius_max_batch_rows = if params_per_op == 0 { + config.max_batch_rows + } else { + ((TIBERIUS_PARAM_MAX as f64 / params_per_op as f64).floor()) as usize + }; + if tiberius_max_batch_rows == 0 { + return Err(SinkError::SqlServer(anyhow!(format!( + "too many column {}", + params_per_op + )))); + } + config.max_batch_rows = std::cmp::min(config.max_batch_rows, tiberius_max_batch_rows); + Ok(Self { + config, + schema, + pk_indices, + is_append_only, + }) + } +} + +impl TryFrom for SqlServerSink { + type Error = SinkError; + + fn try_from(param: SinkParam) -> std::result::Result { + let schema = param.schema(); + let config = SqlServerConfig::from_btreemap(param.properties)?; + SqlServerSink::new( + config, + schema, + param.downstream_pk, + param.sink_type.is_append_only(), + ) + } +} + +impl Sink for SqlServerSink { + type Coordinator = DummySinkCommitCoordinator; + type LogSinker = LogSinkerOf; + + const SINK_NAME: &'static str = SQLSERVER_SINK; + + async fn validate(&self) -> Result<()> { + if !self.is_append_only && self.pk_indices.is_empty() { + return Err(SinkError::Config(anyhow!( + "Primary key not defined for upsert SQL Server sink (please define in `primary_key` field)"))); + } + + for f in self.schema.fields() { + check_data_type_compatibility(&f.data_type)?; + } + + // Query table metadata from SQL Server. + let mut sql_server_table_metadata = HashMap::new(); + let mut sql_client = SqlClient::new(&self.config).await?; + let query_table_metadata_error = || { + SinkError::SqlServer(anyhow!(format!( + "SQL Server table {} metadata error", + self.config.table + ))) + }; + static QUERY_TABLE_METADATA: &str = r#" +SELECT + col.name AS ColumnName, + pk.index_id AS PkIndex +FROM + sys.columns col +LEFT JOIN + sys.index_columns ic ON ic.object_id = col.object_id AND ic.column_id = col.column_id +LEFT JOIN + sys.indexes pk ON pk.object_id = col.object_id AND pk.index_id = ic.index_id AND pk.is_primary_key = 1 +WHERE + col.object_id = OBJECT_ID(@P1) +ORDER BY + col.column_id;"#; + let rows = sql_client + .client + .query(QUERY_TABLE_METADATA, &[&self.config.table]) + .await? + .into_results() + .await?; + for row in rows.into_iter().flatten() { + let mut iter = row.into_iter(); + let ColumnData::String(Some(col_name)) = + iter.next().ok_or_else(query_table_metadata_error)? + else { + return Err(query_table_metadata_error()); + }; + let ColumnData::I32(col_pk_index) = + iter.next().ok_or_else(query_table_metadata_error)? + else { + return Err(query_table_metadata_error()); + }; + sql_server_table_metadata.insert(col_name.into_owned(), col_pk_index.is_some()); + } + + // Validate Column name and Primary Key + for (idx, col) in self.schema.fields().iter().enumerate() { + let rw_is_pk = self.pk_indices.contains(&idx); + match sql_server_table_metadata.get(&col.name) { + None => { + return Err(SinkError::SqlServer(anyhow!(format!( + "column {} not found in the downstream SQL Server table {}", + col.name, self.config.table + )))); + } + Some(sql_server_is_pk) => { + if self.is_append_only { + continue; + } + if rw_is_pk && !*sql_server_is_pk { + return Err(SinkError::SqlServer(anyhow!(format!( + "column {} specified in primary_key mismatches with the downstream SQL Server table {} PK", + col.name, self.config.table, + )))); + } + if !rw_is_pk && *sql_server_is_pk { + return Err(SinkError::SqlServer(anyhow!(format!( + "column {} unspecified in primary_key mismatches with the downstream SQL Server table {} PK", + col.name, self.config.table, + )))); + } + } + } + } + + if !self.is_append_only { + let sql_server_pk_count = sql_server_table_metadata + .values() + .filter(|is_pk| **is_pk) + .count(); + if sql_server_pk_count != self.pk_indices.len() { + return Err(SinkError::SqlServer(anyhow!(format!( + "primary key does not match between RisingWave sink ({}) and SQL Server table {} ({})", + self.pk_indices.len(), + self.config.table, + sql_server_pk_count, + )))); + } + } + + Ok(()) + } + + async fn new_log_sinker(&self, writer_param: SinkWriterParam) -> Result { + Ok(SqlServerSinkWriter::new( + self.config.clone(), + self.schema.clone(), + self.pk_indices.clone(), + self.is_append_only, + ) + .await? + .into_log_sinker(writer_param.sink_metrics)) + } +} + +enum SqlOp { + Insert(OwnedRow), + Merge(OwnedRow), + Delete(OwnedRow), +} + +pub struct SqlServerSinkWriter { + config: SqlServerConfig, + schema: Schema, + pk_indices: Vec, + is_append_only: bool, + sql_client: SqlClient, + ops: Vec, +} + +impl SqlServerSinkWriter { + async fn new( + config: SqlServerConfig, + schema: Schema, + pk_indices: Vec, + is_append_only: bool, + ) -> Result { + let sql_client = SqlClient::new(&config).await?; + let writer = Self { + config, + schema, + pk_indices, + is_append_only, + sql_client, + ops: vec![], + }; + Ok(writer) + } + + async fn delete_one(&mut self, row: RowRef<'_>) -> Result<()> { + if self.ops.len() + 1 >= self.config.max_batch_rows { + self.flush().await?; + } + self.ops.push(SqlOp::Delete(row.into_owned_row())); + Ok(()) + } + + async fn upsert_one(&mut self, row: RowRef<'_>) -> Result<()> { + if self.ops.len() + 1 >= self.config.max_batch_rows { + self.flush().await?; + } + self.ops.push(SqlOp::Merge(row.into_owned_row())); + Ok(()) + } + + async fn insert_one(&mut self, row: RowRef<'_>) -> Result<()> { + if self.ops.len() + 1 >= self.config.max_batch_rows { + self.flush().await?; + } + self.ops.push(SqlOp::Insert(row.into_owned_row())); + Ok(()) + } + + async fn flush(&mut self) -> Result<()> { + use std::fmt::Write; + if self.ops.is_empty() { + return Ok(()); + } + let mut query_str = String::new(); + let col_num = self.schema.fields.len(); + let mut next_param_id = 1; + let non_pk_col_indices = (0..col_num) + .filter(|idx| !self.pk_indices.contains(idx)) + .collect::>(); + let all_col_names = self + .schema + .fields + .iter() + .map(|f| format!("[{}]", f.name)) + .collect::>() + .join(","); + let all_source_col_names = self + .schema + .fields + .iter() + .map(|f| format!("[SOURCE].[{}]", f.name)) + .collect::>() + .join(","); + let pk_match = self + .pk_indices + .iter() + .map(|idx| { + format!( + "[SOURCE].[{}]=[TARGET].[{}]", + &self.schema[*idx].name, &self.schema[*idx].name + ) + }) + .collect::>() + .join(" AND "); + let param_placeholders = |param_id: &mut usize| { + let params = (*param_id..(*param_id + col_num)) + .map(|i| format!("@P{}", i)) + .collect::>() + .join(","); + *param_id += col_num; + params + }; + let set_all_source_col = non_pk_col_indices + .iter() + .map(|idx| { + format!( + "[{}]=[SOURCE].[{}]", + &self.schema[*idx].name, &self.schema[*idx].name + ) + }) + .collect::>() + .join(","); + // TODO: avoid repeating the SQL + for op in &self.ops { + match op { + SqlOp::Insert(_) => { + write!( + &mut query_str, + "INSERT INTO [{}] ({}) VALUES ({});", + self.config.table, + all_col_names, + param_placeholders(&mut next_param_id), + ) + .unwrap(); + } + SqlOp::Merge(_) => { + write!( + &mut query_str, + r#"MERGE [{}] AS [TARGET] + USING (VALUES ({})) AS [SOURCE] ({}) + ON {} + WHEN MATCHED THEN UPDATE SET {} + WHEN NOT MATCHED THEN INSERT ({}) VALUES ({});"#, + self.config.table, + param_placeholders(&mut next_param_id), + all_col_names, + pk_match, + set_all_source_col, + all_col_names, + all_source_col_names, + ) + .unwrap(); + } + SqlOp::Delete(_) => { + write!( + &mut query_str, + r#"DELETE FROM [{}] WHERE {};"#, + self.config.table, + self.pk_indices + .iter() + .map(|idx| { + let condition = + format!("[{}]=@P{}", self.schema[*idx].name, next_param_id); + next_param_id += 1; + condition + }) + .collect::>() + .join(" AND "), + ) + .unwrap(); + } + } + } + + let mut query = Query::new(query_str); + for op in self.ops.drain(..) { + match op { + SqlOp::Insert(row) => { + bind_params(&mut query, row, &self.schema, 0..col_num)?; + } + SqlOp::Merge(row) => { + bind_params(&mut query, row, &self.schema, 0..col_num)?; + } + SqlOp::Delete(row) => { + bind_params( + &mut query, + row, + &self.schema, + self.pk_indices.iter().copied(), + )?; + } + } + } + query.execute(&mut self.sql_client.client).await?; + Ok(()) + } +} + +#[async_trait] +impl SinkWriter for SqlServerSinkWriter { + async fn begin_epoch(&mut self, _epoch: u64) -> Result<()> { + Ok(()) + } + + async fn write_batch(&mut self, chunk: StreamChunk) -> Result<()> { + for (op, row) in chunk.rows() { + match op { + Op::Insert => { + if self.is_append_only { + self.insert_one(row).await?; + } else { + self.upsert_one(row).await?; + } + } + Op::UpdateInsert => { + debug_assert!(!self.is_append_only); + self.upsert_one(row).await?; + } + Op::Delete => { + debug_assert!(!self.is_append_only); + self.delete_one(row).await?; + } + Op::UpdateDelete => {} + } + } + Ok(()) + } + + async fn barrier(&mut self, is_checkpoint: bool) -> Result { + if is_checkpoint { + self.flush().await?; + } + Ok(()) + } + + async fn abort(&mut self) -> Result<()> { + Ok(()) + } + + async fn update_vnode_bitmap(&mut self, _vnode_bitmap: Arc) -> Result<()> { + Ok(()) + } +} + +struct SqlClient { + client: Client>, +} + +impl SqlClient { + async fn new(msconfig: &SqlServerConfig) -> Result { + let mut config = Config::new(); + config.host(&msconfig.host); + config.port(msconfig.port); + config.authentication(AuthMethod::sql_server(&msconfig.user, &msconfig.password)); + config.database(&msconfig.database); + config.trust_cert(); + + let tcp = TcpStream::connect(config.get_addr()).await.unwrap(); + tcp.set_nodelay(true).unwrap(); + let client = Client::connect(config, tcp.compat_write()).await?; + Ok(Self { client }) + } +} + +fn bind_params( + query: &mut Query<'_>, + row: impl Row, + schema: &Schema, + col_indices: impl Iterator, +) -> Result<()> { + use risingwave_common::types::ScalarRefImpl; + for col_idx in col_indices { + match row.datum_at(col_idx) { + Some(data_ref) => match data_ref { + ScalarRefImpl::Int16(v) => query.bind(v), + ScalarRefImpl::Int32(v) => query.bind(v), + ScalarRefImpl::Int64(v) => query.bind(v), + ScalarRefImpl::Float32(v) => query.bind(v.into_inner()), + ScalarRefImpl::Float64(v) => query.bind(v.into_inner()), + ScalarRefImpl::Utf8(v) => query.bind(v.to_owned()), + ScalarRefImpl::Bool(v) => query.bind(v), + ScalarRefImpl::Decimal(v) => match v { + Decimal::Normalized(d) => { + query.bind(decimal_to_sql(&d)); + } + Decimal::NaN | Decimal::PositiveInf | Decimal::NegativeInf => { + tracing::warn!( + "Inf, -Inf, Nan in RisingWave decimal is converted into SQL Server null!" + ); + query.bind(None as Option); + } + }, + ScalarRefImpl::Date(v) => query.bind(v.0), + ScalarRefImpl::Timestamp(v) => query.bind(v.0), + ScalarRefImpl::Timestamptz(v) => query.bind(v.timestamp_micros()), + ScalarRefImpl::Time(v) => query.bind(v.0), + ScalarRefImpl::Bytea(v) => query.bind(v.to_vec()), + ScalarRefImpl::Interval(_) => return Err(data_type_not_supported("Interval")), + ScalarRefImpl::Jsonb(_) => return Err(data_type_not_supported("Jsonb")), + ScalarRefImpl::Struct(_) => return Err(data_type_not_supported("Struct")), + ScalarRefImpl::List(_) => return Err(data_type_not_supported("List")), + ScalarRefImpl::Int256(_) => return Err(data_type_not_supported("Int256")), + ScalarRefImpl::Serial(_) => return Err(data_type_not_supported("Serial")), + }, + None => match schema[col_idx].data_type { + DataType::Boolean => { + query.bind(None as Option); + } + DataType::Int16 => { + query.bind(None as Option); + } + DataType::Int32 => { + query.bind(None as Option); + } + DataType::Int64 => { + query.bind(None as Option); + } + DataType::Float32 => { + query.bind(None as Option); + } + DataType::Float64 => { + query.bind(None as Option); + } + DataType::Decimal => { + query.bind(None as Option); + } + DataType::Date => { + query.bind(None as Option); + } + DataType::Time => { + query.bind(None as Option); + } + DataType::Timestamp => { + query.bind(None as Option); + } + DataType::Timestamptz => { + query.bind(None as Option); + } + DataType::Varchar => { + query.bind(None as Option); + } + DataType::Bytea => { + query.bind(None as Option>); + } + DataType::Interval => return Err(data_type_not_supported("Interval")), + DataType::Struct(_) => return Err(data_type_not_supported("Struct")), + DataType::List(_) => return Err(data_type_not_supported("List")), + DataType::Jsonb => return Err(data_type_not_supported("Jsonb")), + DataType::Serial => return Err(data_type_not_supported("Serial")), + DataType::Int256 => return Err(data_type_not_supported("Int256")), + }, + }; + } + Ok(()) +} + +fn data_type_not_supported(data_type_name: &str) -> SinkError { + SinkError::SqlServer(anyhow!(format!( + "{data_type_name} is not supported in SQL Server" + ))) +} + +fn check_data_type_compatibility(data_type: &DataType) -> Result<()> { + match data_type { + DataType::Boolean + | DataType::Int16 + | DataType::Int32 + | DataType::Int64 + | DataType::Float32 + | DataType::Float64 + | DataType::Decimal + | DataType::Date + | DataType::Varchar + | DataType::Time + | DataType::Timestamp + | DataType::Timestamptz + | DataType::Bytea => Ok(()), + DataType::Interval => Err(data_type_not_supported("Interval")), + DataType::Struct(_) => Err(data_type_not_supported("Struct")), + DataType::List(_) => Err(data_type_not_supported("List")), + DataType::Jsonb => Err(data_type_not_supported("Jsonb")), + DataType::Serial => Err(data_type_not_supported("Serial")), + DataType::Int256 => Err(data_type_not_supported("Int256")), + } +} + +/// The implementation is copied from tiberius crate. +fn decimal_to_sql(decimal: &rust_decimal::Decimal) -> Numeric { + let unpacked = decimal.unpack(); + + let mut value = (((unpacked.hi as u128) << 64) + + ((unpacked.mid as u128) << 32) + + unpacked.lo as u128) as i128; + + if decimal.is_sign_negative() { + value = -value; + } + + Numeric::new_with_scale(value, decimal.scale() as u8) +} diff --git a/src/connector/src/sink/starrocks.rs b/src/connector/src/sink/starrocks.rs index 994e4e43d8172..56c352bfb4a9e 100644 --- a/src/connector/src/sink/starrocks.rs +++ b/src/connector/src/sink/starrocks.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::{BTreeMap, HashMap}; +use std::num::NonZeroU64; use std::sync::Arc; use anyhow::anyhow; @@ -21,30 +22,46 @@ use bytes::Bytes; use mysql_async::prelude::Queryable; use mysql_async::Opts; use risingwave_common::array::{Op, StreamChunk}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::Schema; +use risingwave_common::session_config::sink_decouple::SinkDecouple; use risingwave_common::types::DataType; +use risingwave_pb::connector_service::sink_metadata::Metadata::Serialized; +use risingwave_pb::connector_service::sink_metadata::SerializedMetadata; +use risingwave_pb::connector_service::SinkMetadata; use serde::Deserialize; use serde_derive::Serialize; use serde_json::Value; -use serde_with::serde_as; +use serde_with::{serde_as, DisplayFromStr}; use thiserror_ext::AsReport; +use tokio::task::JoinHandle; +use url::form_urlencoded; use with_options::WithOptions; use super::doris_starrocks_connector::{ - HeaderBuilder, InserterInner, InserterInnerBuilder, DORIS_SUCCESS_STATUS, STARROCKS_DELETE_SIGN, + HeaderBuilder, InserterInner, StarrocksTxnRequestBuilder, STARROCKS_DELETE_SIGN, + STARROCKS_SUCCESS_STATUS, }; use super::encoder::{JsonEncoder, RowEncoder}; -use super::writer::LogSinkerOf; -use super::{SinkError, SinkParam, SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, SINK_TYPE_UPSERT}; -use crate::sink::writer::SinkWriterExt; -use crate::sink::{DummySinkCommitCoordinator, Result, Sink, SinkWriter, SinkWriterParam}; +use super::{ + SinkCommitCoordinator, SinkError, SinkParam, SINK_TYPE_APPEND_ONLY, SINK_TYPE_OPTION, + SINK_TYPE_UPSERT, +}; +use crate::deserialize_optional_u64_from_string; +use crate::sink::catalog::desc::SinkDesc; +use crate::sink::coordinate::CoordinatedSinkWriter; +use crate::sink::decouple_checkpoint_log_sink::DecoupleCheckpointLogSinkerOf; +use crate::sink::{Result, Sink, SinkWriter, SinkWriterParam}; pub const STARROCKS_SINK: &str = "starrocks"; const STARROCK_MYSQL_PREFER_SOCKET: &str = "false"; const STARROCK_MYSQL_MAX_ALLOWED_PACKET: usize = 1024; const STARROCK_MYSQL_WAIT_TIMEOUT: usize = 28800; +const fn _default_stream_load_http_timeout_ms() -> u64 { + 30 * 1000 +} + #[derive(Deserialize, Debug, Clone, WithOptions)] pub struct StarrocksCommon { /// The `StarRocks` host address. @@ -68,8 +85,6 @@ pub struct StarrocksCommon { /// The `StarRocks` table you want to sink data to. #[serde(rename = "starrocks.table")] pub table: String, - #[serde(rename = "starrocks.partial_update")] - pub partial_update: Option, } #[serde_as] @@ -78,10 +93,31 @@ pub struct StarrocksConfig { #[serde(flatten)] pub common: StarrocksCommon, + /// The timeout in milliseconds for stream load http request, defaults to 10 seconds. + #[serde( + rename = "starrocks.stream_load.http.timeout.ms", + default = "_default_stream_load_http_timeout_ms" + )] + #[serde_as(as = "DisplayFromStr")] + pub stream_load_http_timeout_ms: u64, + + /// Set this option to a positive integer n, RisingWave will try to commit data + /// to Starrocks at every n checkpoints by leveraging the + /// [StreamLoad Transaction API](https://docs.starrocks.io/docs/loading/Stream_Load_transaction_interface/), + /// also, in this time, the `sink_decouple` option should be enabled as well. + /// Defaults to 1 if commit_checkpoint_interval <= 0 + #[serde(default, deserialize_with = "deserialize_optional_u64_from_string")] + pub commit_checkpoint_interval: Option, + + /// Enable partial update + #[serde(rename = "starrocks.partial_update")] + pub partial_update: Option, + pub r#type: String, // accept "append-only" or "upsert" } + impl StarrocksConfig { - pub fn from_hashmap(properties: HashMap) -> Result { + pub fn from_btreemap(properties: BTreeMap) -> Result { let config = serde_json::from_value::(serde_json::to_value(properties).unwrap()) .map_err(|e| SinkError::Config(anyhow!(e)))?; @@ -93,12 +129,18 @@ impl StarrocksConfig { SINK_TYPE_UPSERT ))); } + if config.commit_checkpoint_interval == Some(0) { + return Err(SinkError::Config(anyhow!( + "commit_checkpoint_interval must be greater than 0" + ))); + } Ok(config) } } #[derive(Debug)] pub struct StarrocksSink { + param: SinkParam, pub config: StarrocksConfig, schema: Schema, pk_indices: Vec, @@ -106,13 +148,11 @@ pub struct StarrocksSink { } impl StarrocksSink { - pub fn new( - config: StarrocksConfig, - schema: Schema, - pk_indices: Vec, - is_append_only: bool, - ) -> Result { + pub fn new(param: SinkParam, config: StarrocksConfig, schema: Schema) -> Result { + let pk_indices = param.downstream_pk.clone(); + let is_append_only = param.sink_type.is_append_only(); Ok(Self { + param, config, schema, pk_indices, @@ -128,7 +168,7 @@ impl StarrocksSink { ) -> Result<()> { let rw_fields_name = self.schema.fields(); if rw_fields_name.len() > starrocks_columns_desc.len() { - return Err(SinkError::Starrocks("The length of the RisingWave column must be equal or less to the length of the starrocks column".to_string())); + return Err(SinkError::Starrocks("The columns of the sink must be equal to or a superset of the target table's columns.".to_string())); } for i in rw_fields_name { @@ -211,19 +251,33 @@ impl StarrocksSink { } impl Sink for StarrocksSink { - type Coordinator = DummySinkCommitCoordinator; - type LogSinker = LogSinkerOf; + type Coordinator = StarrocksSinkCommitter; + type LogSinker = DecoupleCheckpointLogSinkerOf>; const SINK_NAME: &'static str = STARROCKS_SINK; - async fn new_log_sinker(&self, writer_param: SinkWriterParam) -> Result { - Ok(StarrocksSinkWriter::new( - self.config.clone(), - self.schema.clone(), - self.pk_indices.clone(), - self.is_append_only, - )? - .into_log_sinker(writer_param.sink_metrics)) + fn is_sink_decouple(desc: &SinkDesc, user_specified: &SinkDecouple) -> Result { + let config_decouple = if let Some(interval) = + desc.properties.get("commit_checkpoint_interval") + && interval.parse::().unwrap_or(0) > 1 + { + true + } else { + false + }; + + match user_specified { + SinkDecouple::Default => Ok(config_decouple), + SinkDecouple::Disable => { + if config_decouple { + return Err(SinkError::Config(anyhow!( + "config conflict: StarRocks config `commit_checkpoint_interval` larger than 1 means that sink decouple must be enabled, but session config sink_decouple is disabled" + ))); + } + Ok(false) + } + SinkDecouple::Enable => Ok(true), + } } async fn validate(&self) -> Result<()> { @@ -263,16 +317,80 @@ impl Sink for StarrocksSink { self.check_column_name_and_type(starrocks_columns_desc)?; Ok(()) } + + async fn new_log_sinker(&self, writer_param: SinkWriterParam) -> Result { + let commit_checkpoint_interval = + NonZeroU64::new(self.config.commit_checkpoint_interval.unwrap_or(1)).expect( + "commit_checkpoint_interval should be greater than 0, and it should be checked in config validation", + ); + + let inner = StarrocksSinkWriter::new( + self.config.clone(), + self.schema.clone(), + self.pk_indices.clone(), + self.is_append_only, + writer_param.executor_id, + )?; + let writer = CoordinatedSinkWriter::new( + writer_param + .meta_client + .expect("should have meta client") + .sink_coordinate_client() + .await, + self.param.clone(), + writer_param.vnode_bitmap.ok_or_else(|| { + SinkError::Remote(anyhow!( + "sink needs coordination should not have singleton input" + )) + })?, + inner, + ) + .await?; + + Ok(DecoupleCheckpointLogSinkerOf::new( + writer, + writer_param.sink_metrics, + commit_checkpoint_interval, + )) + } + + async fn new_coordinator(&self) -> Result { + let header = HeaderBuilder::new() + .add_common_header() + .set_user_password( + self.config.common.user.clone(), + self.config.common.password.clone(), + ) + .set_db(self.config.common.database.clone()) + .set_table(self.config.common.table.clone()) + .build(); + + let txn_request_builder = StarrocksTxnRequestBuilder::new( + format!( + "http://{}:{}", + self.config.common.host, self.config.common.http_port + ), + header, + self.config.stream_load_http_timeout_ms, + )?; + Ok(StarrocksSinkCommitter { + client: Arc::new(StarrocksTxnClient::new(txn_request_builder)), + }) + } } pub struct StarrocksSinkWriter { pub config: StarrocksConfig, + #[expect(dead_code)] schema: Schema, + #[expect(dead_code)] pk_indices: Vec, - inserter_innet_builder: InserterInnerBuilder, is_append_only: bool, client: Option, + txn_client: StarrocksTxnClient, row_encoder: JsonEncoder, + executor_id: u64, + curr_txn_label: Option, } impl TryFrom for StarrocksSink { @@ -280,13 +398,8 @@ impl TryFrom for StarrocksSink { fn try_from(param: SinkParam) -> std::result::Result { let schema = param.schema(); - let config = StarrocksConfig::from_hashmap(param.properties)?; - StarrocksSink::new( - config, - schema, - param.downstream_pk, - param.sink_type.is_append_only(), - ) + let config = StarrocksConfig::from_btreemap(param.properties.clone())?; + StarrocksSink::new(param, config, schema) } } @@ -296,6 +409,7 @@ impl StarrocksSinkWriter { schema: Schema, pk_indices: Vec, is_append_only: bool, + executor_id: u64, ) -> Result { let mut fields_name = schema.names_str(); if !is_append_only { @@ -306,24 +420,28 @@ impl StarrocksSinkWriter { .add_common_header() .set_user_password(config.common.user.clone(), config.common.password.clone()) .add_json_format() - .set_partial_update(config.common.partial_update.clone()) + .set_partial_update(config.partial_update.clone()) .set_columns_name(fields_name) + .set_db(config.common.database.clone()) + .set_table(config.common.table.clone()) .build(); - let starrocks_insert_builder = InserterInnerBuilder::new( + let txn_request_builder = StarrocksTxnRequestBuilder::new( format!("http://{}:{}", config.common.host, config.common.http_port), - config.common.database.clone(), - config.common.table.clone(), header, + config.stream_load_http_timeout_ms, )?; + Ok(Self { config, schema: schema.clone(), pk_indices, - inserter_innet_builder: starrocks_insert_builder, is_append_only, client: None, + txn_client: StarrocksTxnClient::new(txn_request_builder), row_encoder: JsonEncoder::new_with_starrocks(schema, None), + executor_id, + curr_txn_label: None, }) } @@ -403,14 +521,46 @@ impl StarrocksSinkWriter { } Ok(()) } + + /// Generating a new transaction label, should be unique across all `SinkWriters` even under rewinding. + #[inline(always)] + fn new_txn_label(&self) -> String { + format!( + "rw-txn-{}-{}", + self.executor_id, + chrono::Utc::now().timestamp_micros() + ) + } } #[async_trait] impl SinkWriter for StarrocksSinkWriter { + type CommitMetadata = Option; + + async fn begin_epoch(&mut self, _epoch: u64) -> Result<()> { + Ok(()) + } + async fn write_batch(&mut self, chunk: StreamChunk) -> Result<()> { + // We check whether start a new transaction in `write_batch`. Therefore, if no data has been written + // within the `commit_checkpoint_interval` period, no meta requests will be made. Otherwise if we request + // `prepare` against an empty transaction, the `StarRocks` will report a `hasn't send any data yet` error. + if self.curr_txn_label.is_none() { + let txn_label = self.new_txn_label(); + tracing::debug!(?txn_label, "begin transaction"); + let txn_label_res = self.txn_client.begin(txn_label.clone()).await?; + assert_eq!( + txn_label, txn_label_res, + "label responding from StarRocks: {} differ from generated one: {}", + txn_label, txn_label_res + ); + self.curr_txn_label = Some(txn_label.clone()); + } if self.client.is_none() { + let txn_label = self.curr_txn_label.clone(); + assert!(txn_label.is_some(), "transaction label is none during load"); self.client = Some(StarrocksClient::new( - self.inserter_innet_builder.build().await?, + self.txn_client.load(txn_label.unwrap()).await?, )); } if self.is_append_only { @@ -420,22 +570,45 @@ impl SinkWriter for StarrocksSinkWriter { } } - async fn begin_epoch(&mut self, _epoch: u64) -> Result<()> { - Ok(()) - } - - async fn abort(&mut self) -> Result<()> { - Ok(()) - } - - async fn barrier(&mut self, _is_checkpoint: bool) -> Result<()> { + async fn barrier(&mut self, is_checkpoint: bool) -> Result> { if self.client.is_some() { + // Here we finish the `/api/transaction/load` request when a barrier is received. Therefore, + // one or more load requests should be made within one commit_checkpoint_interval period. + // StarRocks will take care of merging those splits into a larger one during prepare transaction. + // Thus, only one version will be produced when the transaction is committed. See Stream Load + // transaction interface for more information. let client = self .client .take() .ok_or_else(|| SinkError::Starrocks("Can't find starrocks inserter".to_string()))?; client.finish().await?; } + + if is_checkpoint { + if self.curr_txn_label.is_some() { + let txn_label = self.curr_txn_label.take().unwrap(); + tracing::debug!(?txn_label, "prepare transaction"); + let txn_label_res = self.txn_client.prepare(txn_label.clone()).await?; + assert_eq!( + txn_label, txn_label_res, + "label responding from StarRocks differs from the current one" + ); + Ok(Some(StarrocksWriteResult(Some(txn_label)).try_into()?)) + } else { + // no data was written within previous epoch + Ok(Some(StarrocksWriteResult(None).try_into()?)) + } + } else { + Ok(None) + } + } + + async fn abort(&mut self) -> Result<()> { + if self.curr_txn_label.is_some() { + let txn_label = self.curr_txn_label.take().unwrap(); + tracing::debug!(?txn_label, "rollback transaction"); + self.txn_client.rollback(txn_label).await?; + } Ok(()) } @@ -459,6 +632,11 @@ impl StarrocksSchemaClient { user: String, password: String, ) -> Result { + // username & password may contain special chars, so we need to do URL encoding on them. + // Otherwise, Opts::from_url may report a `Parse error` + let user = form_urlencoded::byte_serialize(user.as_bytes()).collect::(); + let password = form_urlencoded::byte_serialize(password.as_bytes()).collect::(); + let conn_uri = format!( "mysql://{}:{}@{}:{}/{}?prefer_socket={}&max_allowed_packet={}&wait_timeout={}", user, @@ -472,12 +650,12 @@ impl StarrocksSchemaClient { ); let pool = mysql_async::Pool::new( Opts::from_url(&conn_uri) - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?, + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?, ); let conn = pool .get_conn() .await - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?; + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; Ok(Self { table, db, conn }) } @@ -490,7 +668,7 @@ impl StarrocksSchemaClient { query_map.insert(column_name, column_type) }) .await - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?; + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; Ok(query_map) } @@ -502,7 +680,7 @@ impl StarrocksSchemaClient { (table_model, primary_key) }) .await - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))? + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))? .first() .ok_or_else(|| { SinkError::Starrocks(format!( @@ -518,35 +696,39 @@ impl StarrocksSchemaClient { #[derive(Debug, Serialize, Deserialize)] pub struct StarrocksInsertResultResponse { #[serde(rename = "TxnId")] - txn_id: i64, + pub txn_id: Option, + #[serde(rename = "Seq")] + pub seq: Option, #[serde(rename = "Label")] - label: String, + pub label: Option, #[serde(rename = "Status")] - status: String, + pub status: String, #[serde(rename = "Message")] - message: String, + pub message: String, #[serde(rename = "NumberTotalRows")] - number_total_rows: i64, + pub number_total_rows: Option, #[serde(rename = "NumberLoadedRows")] - number_loaded_rows: i64, + pub number_loaded_rows: Option, #[serde(rename = "NumberFilteredRows")] - number_filtered_rows: i32, + pub number_filtered_rows: Option, #[serde(rename = "NumberUnselectedRows")] - number_unselected_rows: i32, + pub number_unselected_rows: Option, #[serde(rename = "LoadBytes")] - load_bytes: i64, + pub load_bytes: Option, #[serde(rename = "LoadTimeMs")] - load_time_ms: i32, + pub load_time_ms: Option, #[serde(rename = "BeginTxnTimeMs")] - begin_txn_time_ms: i32, + pub begin_txn_time_ms: Option, #[serde(rename = "ReadDataTimeMs")] - read_data_time_ms: i32, + pub read_data_time_ms: Option, #[serde(rename = "WriteDataTimeMs")] - write_data_time_ms: i32, + pub write_data_time_ms: Option, #[serde(rename = "CommitAndPublishTimeMs")] - commit_and_publish_time_ms: i32, + pub commit_and_publish_time_ms: Option, #[serde(rename = "StreamLoadPlanTimeMs")] - stream_load_plan_time_ms: Option, + pub stream_load_plan_time_ms: Option, + #[serde(rename = "ExistingJobStatus")] + pub existing_job_status: Option, } pub struct StarrocksClient { @@ -565,9 +747,9 @@ impl StarrocksClient { pub async fn finish(self) -> Result { let raw = self.insert.finish().await?; let res: StarrocksInsertResultResponse = serde_json::from_slice(&raw) - .map_err(|err| SinkError::DorisStarrocksConnect(err.into()))?; + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; - if !DORIS_SUCCESS_STATUS.contains(&res.status.as_str()) { + if !STARROCKS_SUCCESS_STATUS.contains(&res.status.as_str()) { return Err(SinkError::DorisStarrocksConnect(anyhow::anyhow!( "Insert error: {:?}", res.message, @@ -576,3 +758,140 @@ impl StarrocksClient { Ok(res) } } + +pub struct StarrocksTxnClient { + request_builder: StarrocksTxnRequestBuilder, +} + +impl StarrocksTxnClient { + pub fn new(request_builder: StarrocksTxnRequestBuilder) -> Self { + Self { request_builder } + } + + fn check_response_and_extract_label(&self, res: Bytes) -> Result { + let res: StarrocksInsertResultResponse = serde_json::from_slice(&res) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + if !STARROCKS_SUCCESS_STATUS.contains(&res.status.as_str()) { + return Err(SinkError::DorisStarrocksConnect(anyhow::anyhow!( + "transaction error: {:?}", + res.message, + ))); + } + res.label.ok_or_else(|| { + SinkError::DorisStarrocksConnect(anyhow::anyhow!("Can't get label from response")) + }) + } + + pub async fn begin(&self, label: String) -> Result { + let res = self + .request_builder + .build_begin_request_sender(label)? + .send() + .await?; + self.check_response_and_extract_label(res) + } + + pub async fn prepare(&self, label: String) -> Result { + let res = self + .request_builder + .build_prepare_request_sender(label)? + .send() + .await?; + self.check_response_and_extract_label(res) + } + + pub async fn commit(&self, label: String) -> Result { + let res = self + .request_builder + .build_commit_request_sender(label)? + .send() + .await?; + self.check_response_and_extract_label(res) + } + + pub async fn rollback(&self, label: String) -> Result { + let res = self + .request_builder + .build_rollback_request_sender(label)? + .send() + .await?; + self.check_response_and_extract_label(res) + } + + pub async fn load(&self, label: String) -> Result { + self.request_builder.build_txn_inserter(label).await + } +} + +struct StarrocksWriteResult(Option); + +impl TryFrom for SinkMetadata { + type Error = SinkError; + + fn try_from(value: StarrocksWriteResult) -> std::result::Result { + match value.0 { + Some(label) => { + let metadata = label.into_bytes(); + Ok(SinkMetadata { + metadata: Some(Serialized(SerializedMetadata { metadata })), + }) + } + None => Ok(SinkMetadata { metadata: None }), + } + } +} + +impl TryFrom for StarrocksWriteResult { + type Error = SinkError; + + fn try_from(value: SinkMetadata) -> std::result::Result { + if let Some(Serialized(v)) = value.metadata { + Ok(StarrocksWriteResult(Some( + String::from_utf8(v.metadata) + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?, + ))) + } else { + Ok(StarrocksWriteResult(None)) + } + } +} + +pub struct StarrocksSinkCommitter { + client: Arc, +} + +#[async_trait::async_trait] +impl SinkCommitCoordinator for StarrocksSinkCommitter { + async fn init(&mut self) -> Result<()> { + tracing::info!("Starrocks commit coordinator inited."); + Ok(()) + } + + async fn commit(&mut self, epoch: u64, metadata: Vec) -> Result<()> { + let write_results = metadata + .into_iter() + .map(TryFrom::try_from) + .collect::>>()?; + + let txn_labels = write_results + .into_iter() + .filter_map(|v| v.0) + .collect::>(); + + tracing::debug!(?epoch, ?txn_labels, "commit transaction"); + + if !txn_labels.is_empty() { + let join_handles = txn_labels + .into_iter() + .map(|txn_label| { + let client = self.client.clone(); + tokio::spawn(async move { client.commit(txn_label).await }) + }) + .collect::>>>(); + futures::future::try_join_all(join_handles) + .await + .map_err(|err| SinkError::DorisStarrocksConnect(anyhow!(err)))?; + } + Ok(()) + } +} diff --git a/src/connector/src/sink/trivial.rs b/src/connector/src/sink/trivial.rs index a3c5a1bd7e2ce..0cfa82c5c4d19 100644 --- a/src/connector/src/sink/trivial.rs +++ b/src/connector/src/sink/trivial.rs @@ -75,19 +75,15 @@ impl Sink for TrivialSink { #[async_trait] impl LogSinker for TrivialSink { - async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result<()> { + async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result { loop { let (epoch, item) = log_reader.next_item().await?; match item { LogStoreReadItem::StreamChunk { chunk_id, .. } => { - log_reader - .truncate(TruncateOffset::Chunk { epoch, chunk_id }) - .await?; + log_reader.truncate(TruncateOffset::Chunk { epoch, chunk_id })?; } LogStoreReadItem::Barrier { .. } => { - log_reader - .truncate(TruncateOffset::Barrier { epoch }) - .await?; + log_reader.truncate(TruncateOffset::Barrier { epoch })?; } LogStoreReadItem::UpdateVnodeBitmap(_) => {} } diff --git a/src/connector/src/sink/writer.rs b/src/connector/src/sink/writer.rs index 1d8142061f35b..7057fa8c8b510 100644 --- a/src/connector/src/sink/writer.rs +++ b/src/connector/src/sink/writer.rs @@ -21,7 +21,7 @@ use async_trait::async_trait; use futures::future::{select, Either}; use futures::TryFuture; use risingwave_common::array::StreamChunk; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use rw_futures_util::drop_either_future; use crate::sink::encoder::SerTo; @@ -126,7 +126,7 @@ impl LogSinkerOf { #[async_trait] impl> LogSinker for LogSinkerOf { - async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result<()> { + async fn consume_log_and_sink(self, log_reader: &mut impl SinkLogReader) -> Result { let mut sink_writer = self.writer; let sink_metrics = self.sink_metrics; #[derive(Debug)] @@ -199,9 +199,7 @@ impl> LogSinker for LogSinkerOf { sink_metrics .sink_commit_duration_metrics .observe(start_time.elapsed().as_millis() as f64); - log_reader - .truncate(TruncateOffset::Barrier { epoch }) - .await?; + log_reader.truncate(TruncateOffset::Barrier { epoch })?; } else { sink_writer.barrier(false).await?; } @@ -244,7 +242,7 @@ impl AsyncTruncateLogSinkerOf { #[async_trait] impl LogSinker for AsyncTruncateLogSinkerOf { - async fn consume_log_and_sink(mut self, log_reader: &mut impl SinkLogReader) -> Result<()> { + async fn consume_log_and_sink(mut self, log_reader: &mut impl SinkLogReader) -> Result { loop { let select_result = drop_either_future( select( @@ -270,7 +268,7 @@ impl LogSinker for AsyncTruncateLogSinkerOf { } Either::Right(offset_result) => { let offset = offset_result?; - log_reader.truncate(offset).await?; + log_reader.truncate(offset)?; } } } diff --git a/src/connector/src/source/base.rs b/src/connector/src/source/base.rs index 7a78ddbce3a9f..707678b665992 100644 --- a/src/connector/src/source/base.rs +++ b/src/connector/src/source/base.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::sync::Arc; use anyhow::anyhow; @@ -42,7 +42,6 @@ use super::nexmark::source::message::NexmarkMeta; use super::{GCS_CONNECTOR, OPENDAL_S3_CONNECTOR, POSIX_FS_CONNECTOR}; use crate::error::ConnectorResult as Result; use crate::parser::ParserConfig; -pub(crate) use crate::source::common::CommonSplitReader; use crate::source::filesystem::FsPageItem; use crate::source::monitor::EnumeratorMetrics; use crate::source::SplitImpl::{CitusCdc, MongodbCdc, MysqlCdc, PostgresCdc}; @@ -56,15 +55,18 @@ const SPLIT_TYPE_FIELD: &str = "split_type"; const SPLIT_INFO_FIELD: &str = "split_info"; pub const UPSTREAM_SOURCE_KEY: &str = "connector"; -pub trait TryFromHashmap: Sized + UnknownFields { +pub trait TryFromBTreeMap: Sized + UnknownFields { /// Used to initialize the source properties from the raw untyped `WITH` options. - fn try_from_hashmap(props: HashMap, deny_unknown_fields: bool) -> Result; + fn try_from_btreemap( + props: BTreeMap, + deny_unknown_fields: bool, + ) -> Result; } /// Represents `WITH` options for sources. /// /// Each instance should add a `#[derive(with_options::WithOptions)]` marker. -pub trait SourceProperties: TryFromHashmap + Clone + WithOptions { +pub trait SourceProperties: TryFromBTreeMap + Clone + WithOptions { const SOURCE_NAME: &'static str; type Split: SplitMetaData + TryFrom @@ -84,8 +86,11 @@ pub trait UnknownFields { fn unknown_fields(&self) -> HashMap; } -impl TryFromHashmap for P { - fn try_from_hashmap(props: HashMap, deny_unknown_fields: bool) -> Result { +impl TryFromBTreeMap for P { + fn try_from_btreemap( + props: BTreeMap, + deny_unknown_fields: bool, + ) -> Result { let json_value = serde_json::to_value(props)?; let res = serde_json::from_value::

(json_value)?; @@ -131,8 +136,7 @@ pub const MAX_CHUNK_SIZE: usize = 1024; #[derive(Debug, Clone)] pub struct SourceCtrlOpts { - // comes from developer::stream_chunk_size in stream scenario and developer::batch_chunk_size - // in batch scenario + /// The max size of a chunk yielded by source stream. pub chunk_size: usize, /// Rate limit of source pub rate_limit: Option, @@ -319,10 +323,19 @@ pub fn extract_source_struct(info: &PbStreamSourceInfo) -> Result Ok(SourceStruct::new(format, encode)) } +/// Stream of [`SourceMessage`]. pub type BoxSourceStream = BoxStream<'static, crate::error::ConnectorResult>>; -pub trait ChunkSourceStream = - Stream> + Send + 'static; +// Manually expand the trait alias to improve IDE experience. +pub trait ChunkSourceStream: + Stream> + Send + 'static +{ +} +impl ChunkSourceStream for T where + T: Stream> + Send + 'static +{ +} + pub type BoxChunkSourceStream = BoxStream<'static, crate::error::ConnectorResult>; pub type BoxTryStream = BoxStream<'static, crate::error::ConnectorResult>; @@ -369,12 +382,12 @@ impl ConnectorProperties { impl ConnectorProperties { /// Creates typed source properties from the raw `WITH` properties. /// - /// It checks the `connector` field, and them dispatches to the corresponding type's `try_from_hashmap` method. + /// It checks the `connector` field, and them dispatches to the corresponding type's `try_from_btreemap` method. /// /// `deny_unknown_fields`: Since `WITH` options are persisted in meta, we do not deny unknown fields when restoring from /// existing data to avoid breaking backwards compatibility. We only deny unknown fields when creating new sources. pub fn extract( - mut with_properties: HashMap, + mut with_properties: BTreeMap, deny_unknown_fields: bool, ) -> Result { let connector = with_properties @@ -383,7 +396,7 @@ impl ConnectorProperties { match_source_name_str!( connector.to_lowercase().as_str(), PropType, - PropType::try_from_hashmap(with_properties, deny_unknown_fields) + PropType::try_from_btreemap(with_properties, deny_unknown_fields) .map(ConnectorProperties::from), |other| bail!("connector '{}' is not supported", other) ) @@ -548,6 +561,19 @@ pub struct SourceMessage { pub meta: SourceMeta, } +impl SourceMessage { + /// Create a dummy `SourceMessage` with all fields unset for testing purposes. + pub fn dummy() -> Self { + Self { + key: None, + payload: None, + offset: "".to_string(), + split_id: "".into(), + meta: SourceMeta::Empty, + } + } +} + #[derive(Debug, Clone)] pub enum SourceMeta { Kafka(KafkaMeta), @@ -658,7 +684,7 @@ mod tests { #[test] fn test_extract_nexmark_config() { - let props: HashMap = convert_args!(hashmap!( + let props = convert_args!(btreemap!( "connector" => "nexmark", "nexmark.table.type" => "Person", "nexmark.split.num" => "1", @@ -676,7 +702,7 @@ mod tests { #[test] fn test_extract_kafka_config() { - let props: HashMap = convert_args!(hashmap!( + let props = convert_args!(btreemap!( "connector" => "kafka", "properties.bootstrap.server" => "b1,b2", "topic" => "test", @@ -686,11 +712,11 @@ mod tests { let props = ConnectorProperties::extract(props, true).unwrap(); if let ConnectorProperties::Kafka(k) = props { - let hashmap: HashMap = hashmap! { + let btreemap = btreemap! { "b-1:9092".to_string() => "dns-1".to_string(), "b-2:9092".to_string() => "dns-2".to_string(), }; - assert_eq!(k.privatelink_common.broker_rewrite_map, Some(hashmap)); + assert_eq!(k.privatelink_common.broker_rewrite_map, Some(btreemap)); } else { panic!("extract kafka config failed"); } @@ -698,7 +724,7 @@ mod tests { #[test] fn test_extract_cdc_properties() { - let user_props_mysql: HashMap = convert_args!(hashmap!( + let user_props_mysql = convert_args!(btreemap!( "connector" => "mysql-cdc", "database.hostname" => "127.0.0.1", "database.port" => "3306", @@ -708,7 +734,7 @@ mod tests { "table.name" => "products", )); - let user_props_postgres: HashMap = convert_args!(hashmap!( + let user_props_postgres = convert_args!(btreemap!( "connector" => "postgres-cdc", "database.hostname" => "127.0.0.1", "database.port" => "5432", diff --git a/src/connector/src/source/cdc/enumerator/mod.rs b/src/connector/src/source/cdc/enumerator/mod.rs index 98018b6b6a113..1c0237b0654c7 100644 --- a/src/connector/src/source/cdc/enumerator/mod.rs +++ b/src/connector/src/source/cdc/enumerator/mod.rs @@ -22,12 +22,13 @@ use itertools::Itertools; use prost::Message; use risingwave_common::util::addr::HostAddr; use risingwave_jni_core::call_static_method; -use risingwave_jni_core::jvm_runtime::JVM; +use risingwave_jni_core::jvm_runtime::{execute_with_jni_env, JVM}; use risingwave_pb::connector_service::{SourceType, ValidateSourceRequest, ValidateSourceResponse}; use crate::error::ConnectorResult; use crate::source::cdc::{ - CdcProperties, CdcSourceTypeTrait, Citus, DebeziumCdcSplit, Mongodb, Mysql, Postgres, + table_schema_exclude_additional_columns, CdcProperties, CdcSourceTypeTrait, Citus, + DebeziumCdcSplit, Mongodb, Mysql, Postgres, }; use crate::source::{SourceEnumeratorContextRef, SplitEnumerator}; @@ -69,39 +70,44 @@ where SourceType::from(T::source_type()) ); + let jvm = JVM.get_or_init()?; let source_id = context.info.source_id; tokio::task::spawn_blocking(move || -> anyhow::Result<()> { - let mut env = JVM.get_or_init()?.attach_current_thread()?; - - let validate_source_request = ValidateSourceRequest { - source_id: source_id as u64, - source_type: props.get_source_type_pb() as _, - properties: props.properties, - table_schema: Some(props.table_schema), - is_source_job: props.is_cdc_source_job, - is_backfill_table: props.is_backfill_table, - }; - - let validate_source_request_bytes = - env.byte_array_from_slice(&Message::encode_to_vec(&validate_source_request))?; - - let validate_source_response_bytes = call_static_method!( - env, - {com.risingwave.connector.source.JniSourceValidateHandler}, - {byte[] validate(byte[] validateSourceRequestBytes)}, - &validate_source_request_bytes - )?; - - let validate_source_response: ValidateSourceResponse = Message::decode( - risingwave_jni_core::to_guarded_slice(&validate_source_response_bytes, &mut env)? - .deref(), - )?; - - if let Some(error) = validate_source_response.error { - return Err(anyhow!(error.error_message).context("source cannot pass validation")); - } - - Ok(()) + execute_with_jni_env(jvm, |env| { + let validate_source_request = ValidateSourceRequest { + source_id: source_id as u64, + source_type: props.get_source_type_pb() as _, + properties: props.properties, + table_schema: Some(table_schema_exclude_additional_columns( + &props.table_schema, + )), + is_source_job: props.is_cdc_source_job, + is_backfill_table: props.is_backfill_table, + }; + + let validate_source_request_bytes = + env.byte_array_from_slice(&Message::encode_to_vec(&validate_source_request))?; + + let validate_source_response_bytes = call_static_method!( + env, + {com.risingwave.connector.source.JniSourceValidateHandler}, + {byte[] validate(byte[] validateSourceRequestBytes)}, + &validate_source_request_bytes + )?; + + let validate_source_response: ValidateSourceResponse = Message::decode( + risingwave_jni_core::to_guarded_slice(&validate_source_response_bytes, env)? + .deref(), + )?; + + if let Some(error) = validate_source_response.error { + return Err( + anyhow!(error.error_message).context("source cannot pass validation") + ); + } + + Ok(()) + }) }) .await .context("failed to validate source")??; diff --git a/src/connector/src/source/cdc/external/mock_external_table.rs b/src/connector/src/source/cdc/external/mock_external_table.rs index 4a652e1c1628c..7242f5614d409 100644 --- a/src/connector/src/source/cdc/external/mock_external_table.rs +++ b/src/connector/src/source/cdc/external/mock_external_table.rs @@ -38,10 +38,20 @@ impl MockExternalTableReader { } } + pub fn get_normalized_table_name(_table_name: &SchemaTableName) -> String { + "`mock_table`".to_string() + } + pub fn get_cdc_offset_parser() -> CdcOffsetParseFunc { - Box::new(move |_| Ok(CdcOffset::MySql(MySqlOffset::default()))) + Box::new(move |offset| { + Ok(CdcOffset::MySql(MySqlOffset::parse_debezium_offset( + offset, + )?)) + }) } + /// The snapshot will emit to downstream all in once, because it is too small. + /// After that we will emit the buffered upstream chunks all in one. #[try_stream(boxed, ok = OwnedRow, error = ConnectorError)] async fn snapshot_read_inner(&self) { let snap_idx = self @@ -49,18 +59,18 @@ impl MockExternalTableReader { .fetch_add(1, std::sync::atomic::Ordering::Relaxed); println!("snapshot read: idx {}", snap_idx); - let snap0 = vec![OwnedRow::new(vec![ - Some(ScalarImpl::Int64(1)), - Some(ScalarImpl::Float64(1.0001.into())), - ])]; - let snap1 = vec![ + let snap0 = vec![ OwnedRow::new(vec![ Some(ScalarImpl::Int64(1)), - Some(ScalarImpl::Float64(10.01.into())), + Some(ScalarImpl::Float64(1.0001.into())), + ]), + OwnedRow::new(vec![ + Some(ScalarImpl::Int64(1)), + Some(ScalarImpl::Float64(11.00.into())), ]), OwnedRow::new(vec![ Some(ScalarImpl::Int64(2)), - Some(ScalarImpl::Float64(2.02.into())), + Some(ScalarImpl::Float64(22.00.into())), ]), OwnedRow::new(vec![ Some(ScalarImpl::Int64(5)), @@ -76,7 +86,7 @@ impl MockExternalTableReader { ]), ]; - let snapshots = [snap0, snap1]; + let snapshots = [snap0]; if snap_idx >= snapshots.len() { return Ok(()); } @@ -88,10 +98,6 @@ impl MockExternalTableReader { } impl ExternalTableReader for MockExternalTableReader { - fn get_normalized_table_name(&self, _table_name: &SchemaTableName) -> String { - "`mock_table`".to_string() - } - async fn current_cdc_offset(&self) -> ConnectorResult { static IDX: AtomicUsize = AtomicUsize::new(0); diff --git a/src/connector/src/source/cdc/external/mod.rs b/src/connector/src/source/cdc/external/mod.rs index bd2b623731c7c..528e14bd60229 100644 --- a/src/connector/src/source/cdc/external/mod.rs +++ b/src/connector/src/source/cdc/external/mod.rs @@ -13,33 +13,34 @@ // limitations under the License. pub mod mock_external_table; -mod postgres; +pub mod postgres; #[cfg(not(madsim))] mod maybe_tls_connector; +pub mod mysql; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::fmt; -use anyhow::Context; +use anyhow::anyhow; +use futures::pin_mut; use futures::stream::BoxStream; -use futures::{pin_mut, StreamExt}; use futures_async_stream::try_stream; -use itertools::Itertools; -use mysql_async::prelude::*; -use mysql_common::params::Params; -use mysql_common::value::Value; use risingwave_common::bail; -use risingwave_common::catalog::{Schema, OFFSET_COLUMN_NAME}; +use risingwave_common::catalog::{ColumnDesc, Schema}; use risingwave_common::row::OwnedRow; -use risingwave_common::types::DataType; -use risingwave_common::util::iter_util::ZipEqFast; use serde_derive::{Deserialize, Serialize}; use crate::error::{ConnectorError, ConnectorResult}; use crate::parser::mysql_row_to_owned_row; use crate::source::cdc::external::mock_external_table::MockExternalTableReader; -use crate::source::cdc::external::postgres::{PostgresExternalTableReader, PostgresOffset}; +use crate::source::cdc::external::mysql::{ + MySqlExternalTable, MySqlExternalTableReader, MySqlOffset, +}; +use crate::source::cdc::external::postgres::{ + PostgresExternalTable, PostgresExternalTableReader, PostgresOffset, +}; +use crate::source::cdc::CdcSourceType; use crate::WithPropertiesExt; #[derive(Debug)] @@ -67,15 +68,17 @@ impl CdcTableType { pub async fn create_table_reader( &self, - with_properties: HashMap, + config: ExternalTableConfig, schema: Schema, + pk_indices: Vec, + scan_limit: u32, ) -> ConnectorResult { match self { Self::MySql => Ok(ExternalTableReaderImpl::MySql( - MySqlExternalTableReader::new(with_properties, schema).await?, + MySqlExternalTableReader::new(config, schema).await?, )), Self::Postgres => Ok(ExternalTableReaderImpl::Postgres( - PostgresExternalTableReader::new(with_properties, schema).await?, + PostgresExternalTableReader::new(config, schema, pk_indices, scan_limit).await?, )), _ => bail!("invalid external table type: {:?}", *self), } @@ -94,13 +97,6 @@ pub const SCHEMA_NAME_KEY: &str = "schema.name"; pub const DATABASE_NAME_KEY: &str = "database.name"; impl SchemaTableName { - pub fn new(schema_name: String, table_name: String) -> Self { - Self { - schema_name, - table_name, - } - } - pub fn from_properties(properties: &HashMap) -> Self { let table_type = CdcTableType::from_properties(properties); let table_name = properties.get(TABLE_NAME_KEY).cloned().unwrap_or_default(); @@ -125,18 +121,6 @@ impl SchemaTableName { } } -#[derive(Debug, Clone, Default, PartialEq, PartialOrd, Serialize, Deserialize)] -pub struct MySqlOffset { - pub filename: String, - pub position: u64, -} - -impl MySqlOffset { - pub fn new(filename: String, position: u64) -> Self { - Self { filename, position } - } -} - #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] pub enum CdcOffset { MySql(MySqlOffset), @@ -186,29 +170,9 @@ pub struct DebeziumSourceOffset { pub tx_usec: Option, } -impl MySqlOffset { - pub fn parse_debezium_offset(offset: &str) -> ConnectorResult { - let dbz_offset: DebeziumOffset = serde_json::from_str(offset) - .with_context(|| format!("invalid upstream offset: {}", offset))?; - - Ok(Self { - filename: dbz_offset - .source_offset - .file - .context("binlog file not found in offset")?, - position: dbz_offset - .source_offset - .pos - .context("binlog position not found in offset")?, - }) - } -} - pub type CdcOffsetParseFunc = Box ConnectorResult + Send>; pub trait ExternalTableReader { - fn get_normalized_table_name(&self, table_name: &SchemaTableName) -> String; - async fn current_cdc_offset(&self) -> ConnectorResult; fn snapshot_read( @@ -220,24 +184,16 @@ pub trait ExternalTableReader { ) -> BoxStream<'_, ConnectorResult>; } -#[derive(Debug)] pub enum ExternalTableReaderImpl { MySql(MySqlExternalTableReader), Postgres(PostgresExternalTableReader), Mock(MockExternalTableReader), } -#[derive(Debug)] -pub struct MySqlExternalTableReader { - config: ExternalTableConfig, - rw_schema: Schema, - field_names: String, - // use mutex to provide shared mutable access to the connection - conn: tokio::sync::Mutex, -} - #[derive(Debug, Clone, Deserialize)] pub struct ExternalTableConfig { + pub connector: String, + #[serde(rename = "hostname")] pub host: String, pub port: String, @@ -250,269 +206,51 @@ pub struct ExternalTableConfig { #[serde(rename = "table.name")] pub table: String, /// `ssl.mode` specifies the SSL/TLS encryption level for secure communication with Postgres. - /// Choices include `disable`, `prefer`, and `require`. + /// Choices include `disabled`, `preferred`, and `required`. /// This field is optional. #[serde(rename = "ssl.mode", default = "Default::default")] pub sslmode: SslMode, } +impl ExternalTableConfig { + pub fn try_from_btreemap( + connect_properties: BTreeMap, + ) -> ConnectorResult { + let json_value = serde_json::to_value(connect_properties)?; + let config = serde_json::from_value::(json_value)?; + Ok(config) + } +} + #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "lowercase")] pub enum SslMode { - Disable, - Prefer, - Require, + #[serde(alias = "disable")] + Disabled, + #[serde(alias = "prefer")] + Preferred, + #[serde(alias = "require")] + Required, } impl Default for SslMode { fn default() -> Self { - // default to `disable` for backward compatibility - Self::Disable + // default to `disabled` for backward compatibility + Self::Disabled } } impl fmt::Display for SslMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { - SslMode::Disable => "disable", - SslMode::Prefer => "prefer", - SslMode::Require => "require", + SslMode::Disabled => "disabled", + SslMode::Preferred => "preferred", + SslMode::Required => "required", }) } } -impl ExternalTableReader for MySqlExternalTableReader { - fn get_normalized_table_name(&self, table_name: &SchemaTableName) -> String { - // schema name is the database name in mysql - format!("`{}`.`{}`", table_name.schema_name, table_name.table_name) - } - - async fn current_cdc_offset(&self) -> ConnectorResult { - let mut conn = self.conn.lock().await; - - let sql = "SHOW MASTER STATUS".to_string(); - let mut rs = conn.query::(sql).await?; - let row = rs - .iter_mut() - .exactly_one() - .ok() - .context("expect exactly one row when reading binlog offset")?; - - Ok(CdcOffset::MySql(MySqlOffset { - filename: row.take("File").unwrap(), - position: row.take("Position").unwrap(), - })) - } - - fn snapshot_read( - &self, - table_name: SchemaTableName, - start_pk: Option, - primary_keys: Vec, - limit: u32, - ) -> BoxStream<'_, ConnectorResult> { - self.snapshot_read_inner(table_name, start_pk, primary_keys, limit) - } -} - -impl MySqlExternalTableReader { - pub async fn new( - with_properties: HashMap, - rw_schema: Schema, - ) -> ConnectorResult { - tracing::debug!(?rw_schema, "create mysql external table reader"); - - let config = serde_json::from_value::( - serde_json::to_value(with_properties).unwrap(), - ) - .context("failed to extract mysql connector properties")?; - - let database_url = format!( - "mysql://{}:{}@{}:{}/{}", - config.username, config.password, config.host, config.port, config.database - ); - let opts = mysql_async::Opts::from_url(&database_url).map_err(mysql_async::Error::Url)?; - let conn = mysql_async::Conn::new(opts).await?; - - let field_names = rw_schema - .fields - .iter() - .filter(|f| f.name != OFFSET_COLUMN_NAME) - .map(|f| Self::quote_column(f.name.as_str())) - .join(","); - - Ok(Self { - config, - rw_schema, - field_names, - conn: tokio::sync::Mutex::new(conn), - }) - } - - pub fn get_cdc_offset_parser() -> CdcOffsetParseFunc { - Box::new(move |offset| { - Ok(CdcOffset::MySql(MySqlOffset::parse_debezium_offset( - offset, - )?)) - }) - } - - #[try_stream(boxed, ok = OwnedRow, error = ConnectorError)] - async fn snapshot_read_inner( - &self, - table_name: SchemaTableName, - start_pk_row: Option, - primary_keys: Vec, - limit: u32, - ) { - let order_key = primary_keys - .iter() - .map(|col| Self::quote_column(col)) - .join(","); - let sql = if start_pk_row.is_none() { - format!( - "SELECT {} FROM {} ORDER BY {} LIMIT {limit}", - self.field_names, - self.get_normalized_table_name(&table_name), - order_key, - ) - } else { - let filter_expr = Self::filter_expression(&primary_keys); - format!( - "SELECT {} FROM {} WHERE {} ORDER BY {} LIMIT {limit}", - self.field_names, - self.get_normalized_table_name(&table_name), - filter_expr, - order_key, - ) - }; - - let mut conn = self.conn.lock().await; - - // Set session timezone to UTC - conn.exec_drop("SET time_zone = \"+00:00\"", ()).await?; - - if start_pk_row.is_none() { - let rs_stream = sql.stream::(&mut *conn).await?; - let row_stream = rs_stream.map(|row| { - // convert mysql row into OwnedRow - let mut row = row?; - Ok::<_, ConnectorError>(mysql_row_to_owned_row(&mut row, &self.rw_schema)) - }); - - pin_mut!(row_stream); - #[for_await] - for row in row_stream { - let row = row?; - yield row; - } - } else { - let field_map = self - .rw_schema - .fields - .iter() - .map(|f| (f.name.as_str(), f.data_type.clone())) - .collect::>(); - - // fill in start primary key params - let params: Vec<_> = primary_keys - .iter() - .zip_eq_fast(start_pk_row.unwrap().into_iter()) - .map(|(pk, datum)| { - if let Some(value) = datum { - let ty = field_map.get(pk.as_str()).unwrap(); - let val = match ty { - DataType::Boolean => Value::from(value.into_bool()), - DataType::Int16 => Value::from(value.into_int16()), - DataType::Int32 => Value::from(value.into_int32()), - DataType::Int64 => Value::from(value.into_int64()), - DataType::Float32 => Value::from(value.into_float32().into_inner()), - DataType::Float64 => Value::from(value.into_float64().into_inner()), - DataType::Varchar => Value::from(String::from(value.into_utf8())), - DataType::Date => Value::from(value.into_date().0), - DataType::Time => Value::from(value.into_time().0), - DataType::Timestamp => Value::from(value.into_timestamp().0), - _ => bail!("unsupported primary key data type: {}", ty), - }; - ConnectorResult::Ok((pk.clone(), val)) - } else { - bail!("primary key {} cannot be null", pk); - } - }) - .try_collect::<_, _, ConnectorError>()?; - - let rs_stream = sql - .with(Params::from(params)) - .stream::(&mut *conn) - .await?; - - let row_stream = rs_stream.map(|row| { - // convert mysql row into OwnedRow - let mut row = row?; - Ok::<_, ConnectorError>(mysql_row_to_owned_row(&mut row, &self.rw_schema)) - }); - - pin_mut!(row_stream); - #[for_await] - for row in row_stream { - let row = row?; - yield row; - } - }; - } - - // mysql cannot leverage the given key to narrow down the range of scan, - // we need to rewrite the comparison conditions by our own. - // (a, b) > (x, y) => (`a` > x) OR ((`a` = x) AND (`b` > y)) - fn filter_expression(columns: &[String]) -> String { - let mut conditions = vec![]; - // push the first condition - conditions.push(format!( - "({} > :{})", - Self::quote_column(&columns[0]), - columns[0] - )); - for i in 2..=columns.len() { - // '=' condition - let mut condition = String::new(); - for (j, col) in columns.iter().enumerate().take(i - 1) { - if j == 0 { - condition.push_str(&format!("({} = :{})", Self::quote_column(col), col)); - } else { - condition.push_str(&format!(" AND ({} = :{})", Self::quote_column(col), col)); - } - } - // '>' condition - condition.push_str(&format!( - " AND ({} > :{})", - Self::quote_column(&columns[i - 1]), - columns[i - 1] - )); - conditions.push(format!("({})", condition)); - } - if columns.len() > 1 { - conditions.join(" OR ") - } else { - conditions.join("") - } - } - - fn quote_column(column: &str) -> String { - format!("`{}`", column) - } -} - impl ExternalTableReader for ExternalTableReaderImpl { - fn get_normalized_table_name(&self, table_name: &SchemaTableName) -> String { - match self { - ExternalTableReaderImpl::MySql(mysql) => mysql.get_normalized_table_name(table_name), - ExternalTableReaderImpl::Postgres(postgres) => { - postgres.get_normalized_table_name(table_name) - } - ExternalTableReaderImpl::Mock(mock) => mock.get_normalized_table_name(table_name), - } - } - async fn current_cdc_offset(&self) -> ConnectorResult { match self { ExternalTableReaderImpl::MySql(mysql) => mysql.current_cdc_offset().await, @@ -572,94 +310,36 @@ impl ExternalTableReaderImpl { } } -#[cfg(test)] -mod tests { - - use futures::pin_mut; - use futures_async_stream::for_await; - use maplit::{convert_args, hashmap}; - use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema}; - use risingwave_common::types::DataType; - - use crate::source::cdc::external::{ - CdcOffset, ExternalTableReader, MySqlExternalTableReader, MySqlOffset, SchemaTableName, - }; - - #[test] - fn test_mysql_filter_expr() { - let cols = vec!["id".to_string()]; - let expr = MySqlExternalTableReader::filter_expression(&cols); - assert_eq!(expr, "(`id` > :id)"); - - let cols = vec!["aa".to_string(), "bb".to_string(), "cc".to_string()]; - let expr = MySqlExternalTableReader::filter_expression(&cols); - assert_eq!( - expr, - "(`aa` > :aa) OR ((`aa` = :aa) AND (`bb` > :bb)) OR ((`aa` = :aa) AND (`bb` = :bb) AND (`cc` > :cc))" - ); - } +pub enum ExternalTableImpl { + MySql(MySqlExternalTable), + Postgres(PostgresExternalTable), +} - #[test] - fn test_mysql_binlog_offset() { - let off0_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000001", "pos": 105622, "snapshot": true }, "isHeartbeat": false }"#; - let off1_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000007", "pos": 1062363217, "snapshot": true }, "isHeartbeat": false }"#; - let off2_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000007", "pos": 659687560, "snapshot": true }, "isHeartbeat": false }"#; - let off3_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000008", "pos": 7665875, "snapshot": true }, "isHeartbeat": false }"#; - let off4_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000008", "pos": 7665875, "snapshot": true }, "isHeartbeat": false }"#; - - let off0 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off0_str).unwrap()); - let off1 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off1_str).unwrap()); - let off2 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off2_str).unwrap()); - let off3 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off3_str).unwrap()); - let off4 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off4_str).unwrap()); - - assert!(off0 <= off1); - assert!(off1 > off2); - assert!(off2 < off3); - assert_eq!(off3, off4); +impl ExternalTableImpl { + pub async fn connect(config: ExternalTableConfig) -> ConnectorResult { + let cdc_source_type = CdcSourceType::from(config.connector.as_str()); + match cdc_source_type { + CdcSourceType::Mysql => Ok(ExternalTableImpl::MySql( + MySqlExternalTable::connect(config).await?, + )), + CdcSourceType::Postgres => Ok(ExternalTableImpl::Postgres( + PostgresExternalTable::connect(config).await?, + )), + _ => Err(anyhow!("Unsupported cdc connector type: {}", config.connector).into()), + } } - // manual test case - #[ignore] - #[tokio::test] - async fn test_mysql_table_reader() { - let columns = vec![ - ColumnDesc::named("v1", ColumnId::new(1), DataType::Int32), - ColumnDesc::named("v2", ColumnId::new(2), DataType::Decimal), - ColumnDesc::named("v3", ColumnId::new(3), DataType::Varchar), - ColumnDesc::named("v4", ColumnId::new(4), DataType::Date), - ]; - let rw_schema = Schema { - fields: columns.iter().map(Field::from).collect(), - }; - let props = convert_args!(hashmap!( - "hostname" => "localhost", - "port" => "8306", - "username" => "root", - "password" => "123456", - "database.name" => "mytest", - "table.name" => "t1")); - - let reader = MySqlExternalTableReader::new(props, rw_schema) - .await - .unwrap(); - let offset = reader.current_cdc_offset().await.unwrap(); - println!("BinlogOffset: {:?}", offset); - - let off0_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000001", "pos": 105622, "snapshot": true }, "isHeartbeat": false }"#; - let parser = MySqlExternalTableReader::get_cdc_offset_parser(); - println!("parsed offset: {:?}", parser(off0_str).unwrap()); - - let table_name = SchemaTableName { - schema_name: "mytest".to_string(), - table_name: "t1".to_string(), - }; + pub fn column_descs(&self) -> &Vec { + match self { + ExternalTableImpl::MySql(mysql) => mysql.column_descs(), + ExternalTableImpl::Postgres(postgres) => postgres.column_descs(), + } + } - let stream = reader.snapshot_read(table_name, None, vec!["v1".to_string()], 1000); - pin_mut!(stream); - #[for_await] - for row in stream { - println!("OwnedRow: {:?}", row); + pub fn pk_names(&self) -> &Vec { + match self { + ExternalTableImpl::MySql(mysql) => mysql.pk_names(), + ExternalTableImpl::Postgres(postgres) => postgres.pk_names(), } } } diff --git a/src/connector/src/source/cdc/external/mysql.rs b/src/connector/src/source/cdc/external/mysql.rs new file mode 100644 index 0000000000000..e5f53720dd6ee --- /dev/null +++ b/src/connector/src/source/cdc/external/mysql.rs @@ -0,0 +1,569 @@ +// Copyright 2024 RisingWave Labs +// +// 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::HashMap; + +use anyhow::{anyhow, Context}; +use futures::stream::BoxStream; +use futures::{pin_mut, StreamExt}; +use futures_async_stream::try_stream; +use itertools::Itertools; +use mysql_async::prelude::*; +use mysql_common::params::Params; +use mysql_common::value::Value; +use risingwave_common::bail; +use risingwave_common::catalog::{ColumnDesc, ColumnId, Schema, OFFSET_COLUMN_NAME}; +use risingwave_common::row::OwnedRow; +use risingwave_common::types::DataType; +use risingwave_common::util::iter_util::ZipEqFast; +use sea_schema::mysql::def::{ColumnKey, ColumnType}; +use sea_schema::mysql::discovery::SchemaDiscovery; +use sea_schema::mysql::query::SchemaQueryBuilder; +use sea_schema::sea_query::{Alias, IntoIden}; +use serde_derive::{Deserialize, Serialize}; +use sqlx::mysql::MySqlConnectOptions; +use sqlx::MySqlPool; + +use crate::error::{ConnectorError, ConnectorResult}; +use crate::source::cdc::external::{ + mysql_row_to_owned_row, CdcOffset, CdcOffsetParseFunc, DebeziumOffset, ExternalTableConfig, + ExternalTableReader, SchemaTableName, SslMode, +}; + +#[derive(Debug, Clone, Default, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct MySqlOffset { + pub filename: String, + pub position: u64, +} + +impl MySqlOffset { + pub fn new(filename: String, position: u64) -> Self { + Self { filename, position } + } +} + +impl MySqlOffset { + pub fn parse_debezium_offset(offset: &str) -> ConnectorResult { + let dbz_offset: DebeziumOffset = serde_json::from_str(offset) + .with_context(|| format!("invalid upstream offset: {}", offset))?; + + Ok(Self { + filename: dbz_offset + .source_offset + .file + .context("binlog file not found in offset")?, + position: dbz_offset + .source_offset + .pos + .context("binlog position not found in offset")?, + }) + } +} + +pub struct MySqlExternalTable { + column_descs: Vec, + pk_names: Vec, +} + +impl MySqlExternalTable { + pub async fn connect(config: ExternalTableConfig) -> ConnectorResult { + tracing::debug!("connect to mysql"); + let options = MySqlConnectOptions::new() + .username(&config.username) + .password(&config.password) + .host(&config.host) + .port(config.port.parse::().unwrap()) + .database(&config.database) + .ssl_mode(match config.sslmode { + SslMode::Disabled | SslMode::Preferred => sqlx::mysql::MySqlSslMode::Disabled, + SslMode::Required => sqlx::mysql::MySqlSslMode::Required, + }); + + let connection = MySqlPool::connect_with(options).await?; + let mut schema_discovery = SchemaDiscovery::new(connection, config.database.as_str()); + + // discover system version first + let system_info = schema_discovery.discover_system().await?; + schema_discovery.query = SchemaQueryBuilder::new(system_info.clone()); + + let schema = Alias::new(config.database.as_str()).into_iden(); + let table = Alias::new(config.table.as_str()).into_iden(); + let columns = schema_discovery + .discover_columns(schema, table, &system_info) + .await?; + + let mut column_descs = vec![]; + let mut pk_names = vec![]; + for col in columns { + let data_type = type_to_rw_type(&col.col_type)?; + // column name in mysql is case-insensitive, convert to lowercase + let col_name = col.name.to_lowercase(); + column_descs.push(ColumnDesc::named( + col_name.clone(), + ColumnId::placeholder(), + data_type, + )); + if matches!(col.key, ColumnKey::Primary) { + pk_names.push(col_name); + } + } + + if pk_names.is_empty() { + return Err(anyhow!("MySQL table doesn't define the primary key").into()); + } + + Ok(Self { + column_descs, + pk_names, + }) + } + + pub fn column_descs(&self) -> &Vec { + &self.column_descs + } + + pub fn pk_names(&self) -> &Vec { + &self.pk_names + } +} + +fn type_to_rw_type(col_type: &ColumnType) -> ConnectorResult { + let dtype = match col_type { + ColumnType::Serial => DataType::Int32, + ColumnType::Bit(attr) => { + if let Some(1) = attr.maximum { + DataType::Boolean + } else { + return Err( + anyhow!("BIT({}) type not supported", attr.maximum.unwrap_or(0)).into(), + ); + } + } + ColumnType::TinyInt(_) | ColumnType::SmallInt(_) => DataType::Int16, + ColumnType::Bool => DataType::Boolean, + ColumnType::MediumInt(_) => DataType::Int32, + ColumnType::Int(_) => DataType::Int32, + ColumnType::BigInt(_) => DataType::Int64, + ColumnType::Decimal(_) => DataType::Decimal, + ColumnType::Float(_) => DataType::Float32, + ColumnType::Double(_) => DataType::Float64, + ColumnType::Date => DataType::Date, + ColumnType::Time(_) => DataType::Time, + ColumnType::DateTime(_) => DataType::Timestamp, + ColumnType::Timestamp(_) => DataType::Timestamptz, + ColumnType::Year => DataType::Int32, + ColumnType::Char(_) + | ColumnType::NChar(_) + | ColumnType::Varchar(_) + | ColumnType::NVarchar(_) => DataType::Varchar, + ColumnType::Binary(_) | ColumnType::Varbinary(_) => DataType::Bytea, + ColumnType::Text(_) + | ColumnType::TinyText(_) + | ColumnType::MediumText(_) + | ColumnType::LongText(_) => DataType::Varchar, + ColumnType::Blob(_) + | ColumnType::TinyBlob + | ColumnType::MediumBlob + | ColumnType::LongBlob => DataType::Bytea, + ColumnType::Enum(_) => DataType::Varchar, + ColumnType::Json => DataType::Jsonb, + ColumnType::Set(_) => { + return Err(anyhow!("SET type not supported").into()); + } + ColumnType::Geometry(_) => { + return Err(anyhow!("GEOMETRY type not supported").into()); + } + ColumnType::Point(_) => { + return Err(anyhow!("POINT type not supported").into()); + } + ColumnType::LineString(_) => { + return Err(anyhow!("LINE string type not supported").into()); + } + ColumnType::Polygon(_) => { + return Err(anyhow!("POLYGON type not supported").into()); + } + ColumnType::MultiPoint(_) => { + return Err(anyhow!("MULTI POINT type not supported").into()); + } + ColumnType::MultiLineString(_) => { + return Err(anyhow!("MULTI LINE STRING type not supported").into()); + } + ColumnType::MultiPolygon(_) => { + return Err(anyhow!("MULTI POLYGON type not supported").into()); + } + ColumnType::GeometryCollection(_) => { + return Err(anyhow!("GEOMETRY COLLECTION type not supported").into()); + } + ColumnType::Unknown(_) => { + return Err(anyhow!("Unknown MySQL data type").into()); + } + }; + + Ok(dtype) +} + +pub struct MySqlExternalTableReader { + rw_schema: Schema, + field_names: String, + // use mutex to provide shared mutable access to the connection + conn: tokio::sync::Mutex, +} + +impl ExternalTableReader for MySqlExternalTableReader { + async fn current_cdc_offset(&self) -> ConnectorResult { + let mut conn = self.conn.lock().await; + + let sql = "SHOW MASTER STATUS".to_string(); + let mut rs = conn.query::(sql).await?; + let row = rs + .iter_mut() + .exactly_one() + .ok() + .context("expect exactly one row when reading binlog offset")?; + + Ok(CdcOffset::MySql(MySqlOffset { + filename: row.take("File").unwrap(), + position: row.take("Position").unwrap(), + })) + } + + fn snapshot_read( + &self, + table_name: SchemaTableName, + start_pk: Option, + primary_keys: Vec, + limit: u32, + ) -> BoxStream<'_, ConnectorResult> { + self.snapshot_read_inner(table_name, start_pk, primary_keys, limit) + } +} + +impl MySqlExternalTableReader { + pub async fn new(config: ExternalTableConfig, rw_schema: Schema) -> ConnectorResult { + let mut opts_builder = mysql_async::OptsBuilder::default() + .user(Some(config.username)) + .pass(Some(config.password)) + .ip_or_hostname(config.host) + .tcp_port(config.port.parse::().unwrap()) + .db_name(Some(config.database)); + + opts_builder = match config.sslmode { + SslMode::Disabled | SslMode::Preferred => opts_builder.ssl_opts(None), + SslMode::Required => { + let ssl_without_verify = mysql_async::SslOpts::default() + .with_danger_accept_invalid_certs(true) + .with_danger_skip_domain_validation(true); + opts_builder.ssl_opts(Some(ssl_without_verify)) + } + }; + + let conn = mysql_async::Conn::new(mysql_async::Opts::from(opts_builder)).await?; + + let field_names = rw_schema + .fields + .iter() + .filter(|f| f.name != OFFSET_COLUMN_NAME) + .map(|f| Self::quote_column(f.name.as_str())) + .join(","); + + Ok(Self { + rw_schema, + field_names, + conn: tokio::sync::Mutex::new(conn), + }) + } + + pub fn get_normalized_table_name(table_name: &SchemaTableName) -> String { + // schema name is the database name in mysql + format!("`{}`.`{}`", table_name.schema_name, table_name.table_name) + } + + pub fn get_cdc_offset_parser() -> CdcOffsetParseFunc { + Box::new(move |offset| { + Ok(CdcOffset::MySql(MySqlOffset::parse_debezium_offset( + offset, + )?)) + }) + } + + #[try_stream(boxed, ok = OwnedRow, error = ConnectorError)] + async fn snapshot_read_inner( + &self, + table_name: SchemaTableName, + start_pk_row: Option, + primary_keys: Vec, + limit: u32, + ) { + let order_key = primary_keys + .iter() + .map(|col| Self::quote_column(col)) + .join(","); + let sql = if start_pk_row.is_none() { + format!( + "SELECT {} FROM {} ORDER BY {} LIMIT {limit}", + self.field_names, + Self::get_normalized_table_name(&table_name), + order_key, + ) + } else { + let filter_expr = Self::filter_expression(&primary_keys); + format!( + "SELECT {} FROM {} WHERE {} ORDER BY {} LIMIT {limit}", + self.field_names, + Self::get_normalized_table_name(&table_name), + filter_expr, + order_key, + ) + }; + + let mut conn = self.conn.lock().await; + + // Set session timezone to UTC + conn.exec_drop("SET time_zone = \"+00:00\"", ()).await?; + + if start_pk_row.is_none() { + let rs_stream = sql.stream::(&mut *conn).await?; + let row_stream = rs_stream.map(|row| { + // convert mysql row into OwnedRow + let mut row = row?; + Ok::<_, ConnectorError>(mysql_row_to_owned_row(&mut row, &self.rw_schema)) + }); + + pin_mut!(row_stream); + #[for_await] + for row in row_stream { + let row = row?; + yield row; + } + } else { + let field_map = self + .rw_schema + .fields + .iter() + .map(|f| (f.name.as_str(), f.data_type.clone())) + .collect::>(); + + // fill in start primary key params + let params: Vec<_> = primary_keys + .iter() + .zip_eq_fast(start_pk_row.unwrap().into_iter()) + .map(|(pk, datum)| { + if let Some(value) = datum { + let ty = field_map.get(pk.as_str()).unwrap(); + let val = match ty { + DataType::Boolean => Value::from(value.into_bool()), + DataType::Int16 => Value::from(value.into_int16()), + DataType::Int32 => Value::from(value.into_int32()), + DataType::Int64 => Value::from(value.into_int64()), + DataType::Float32 => Value::from(value.into_float32().into_inner()), + DataType::Float64 => Value::from(value.into_float64().into_inner()), + DataType::Varchar => Value::from(String::from(value.into_utf8())), + DataType::Date => Value::from(value.into_date().0), + DataType::Time => Value::from(value.into_time().0), + DataType::Timestamp => Value::from(value.into_timestamp().0), + _ => bail!("unsupported primary key data type: {}", ty), + }; + ConnectorResult::Ok((pk.to_lowercase(), val)) + } else { + bail!("primary key {} cannot be null", pk); + } + }) + .try_collect::<_, _, ConnectorError>()?; + + tracing::debug!("snapshot read params: {:?}", ¶ms); + let rs_stream = sql + .with(Params::from(params)) + .stream::(&mut *conn) + .await?; + + let row_stream = rs_stream.map(|row| { + // convert mysql row into OwnedRow + let mut row = row?; + Ok::<_, ConnectorError>(mysql_row_to_owned_row(&mut row, &self.rw_schema)) + }); + + pin_mut!(row_stream); + #[for_await] + for row in row_stream { + let row = row?; + yield row; + } + }; + } + + // mysql cannot leverage the given key to narrow down the range of scan, + // we need to rewrite the comparison conditions by our own. + // (a, b) > (x, y) => (`a` > x) OR ((`a` = x) AND (`b` > y)) + fn filter_expression(columns: &[String]) -> String { + let mut conditions = vec![]; + // push the first condition + conditions.push(format!( + "({} > :{})", + Self::quote_column(&columns[0]), + columns[0].to_lowercase() + )); + for i in 2..=columns.len() { + // '=' condition + let mut condition = String::new(); + for (j, col) in columns.iter().enumerate().take(i - 1) { + if j == 0 { + condition.push_str(&format!( + "({} = :{})", + Self::quote_column(col), + col.to_lowercase() + )); + } else { + condition.push_str(&format!( + " AND ({} = :{})", + Self::quote_column(col), + col.to_lowercase() + )); + } + } + // '>' condition + condition.push_str(&format!( + " AND ({} > :{})", + Self::quote_column(&columns[i - 1]), + columns[i - 1].to_lowercase() + )); + conditions.push(format!("({})", condition)); + } + if columns.len() > 1 { + conditions.join(" OR ") + } else { + conditions.join("") + } + } + + fn quote_column(column: &str) -> String { + format!("`{}`", column) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use futures::pin_mut; + use futures_async_stream::for_await; + use maplit::{convert_args, hashmap}; + use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema}; + use risingwave_common::types::DataType; + + use crate::source::cdc::external::mysql::MySqlExternalTable; + use crate::source::cdc::external::{ + CdcOffset, ExternalTableConfig, ExternalTableReader, MySqlExternalTableReader, MySqlOffset, + SchemaTableName, + }; + + #[ignore] + #[tokio::test] + async fn test_mysql_schema() { + let config = ExternalTableConfig { + connector: "mysql-cdc".to_string(), + host: "localhost".to_string(), + port: "8306".to_string(), + username: "root".to_string(), + password: "123456".to_string(), + database: "mydb".to_string(), + schema: "".to_string(), + table: "part".to_string(), + sslmode: Default::default(), + }; + + let table = MySqlExternalTable::connect(config).await.unwrap(); + println!("columns: {:?}", &table.column_descs); + println!("primary keys: {:?}", &table.pk_names); + } + + #[test] + fn test_mysql_filter_expr() { + let cols = vec!["id".to_string()]; + let expr = MySqlExternalTableReader::filter_expression(&cols); + assert_eq!(expr, "(`id` > :id)"); + + let cols = vec!["aa".to_string(), "bb".to_string(), "cc".to_string()]; + let expr = MySqlExternalTableReader::filter_expression(&cols); + assert_eq!( + expr, + "(`aa` > :aa) OR ((`aa` = :aa) AND (`bb` > :bb)) OR ((`aa` = :aa) AND (`bb` = :bb) AND (`cc` > :cc))" + ); + } + + #[test] + fn test_mysql_binlog_offset() { + let off0_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000001", "pos": 105622, "snapshot": true }, "isHeartbeat": false }"#; + let off1_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000007", "pos": 1062363217, "snapshot": true }, "isHeartbeat": false }"#; + let off2_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000007", "pos": 659687560, "snapshot": true }, "isHeartbeat": false }"#; + let off3_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000008", "pos": 7665875, "snapshot": true }, "isHeartbeat": false }"#; + let off4_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000008", "pos": 7665875, "snapshot": true }, "isHeartbeat": false }"#; + + let off0 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off0_str).unwrap()); + let off1 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off1_str).unwrap()); + let off2 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off2_str).unwrap()); + let off3 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off3_str).unwrap()); + let off4 = CdcOffset::MySql(MySqlOffset::parse_debezium_offset(off4_str).unwrap()); + + assert!(off0 <= off1); + assert!(off1 > off2); + assert!(off2 < off3); + assert_eq!(off3, off4); + } + + // manual test case + #[ignore] + #[tokio::test] + async fn test_mysql_table_reader() { + let columns = vec![ + ColumnDesc::named("v1", ColumnId::new(1), DataType::Int32), + ColumnDesc::named("v2", ColumnId::new(2), DataType::Decimal), + ColumnDesc::named("v3", ColumnId::new(3), DataType::Varchar), + ColumnDesc::named("v4", ColumnId::new(4), DataType::Date), + ]; + let rw_schema = Schema { + fields: columns.iter().map(Field::from).collect(), + }; + let props: HashMap = convert_args!(hashmap!( + "hostname" => "localhost", + "port" => "8306", + "username" => "root", + "password" => "123456", + "database.name" => "mytest", + "table.name" => "t1")); + + let config = + serde_json::from_value::(serde_json::to_value(props).unwrap()) + .unwrap(); + let reader = MySqlExternalTableReader::new(config, rw_schema) + .await + .unwrap(); + let offset = reader.current_cdc_offset().await.unwrap(); + println!("BinlogOffset: {:?}", offset); + + let off0_str = r#"{ "sourcePartition": { "server": "test" }, "sourceOffset": { "ts_sec": 1670876905, "file": "binlog.000001", "pos": 105622, "snapshot": true }, "isHeartbeat": false }"#; + let parser = MySqlExternalTableReader::get_cdc_offset_parser(); + println!("parsed offset: {:?}", parser(off0_str).unwrap()); + let table_name = SchemaTableName { + schema_name: "mytest".to_string(), + table_name: "t1".to_string(), + }; + + let stream = reader.snapshot_read(table_name, None, vec!["v1".to_string()], 1000); + pin_mut!(stream); + #[for_await] + for row in stream { + println!("OwnedRow: {:?}", row); + } + } +} diff --git a/src/connector/src/source/cdc/external/postgres.rs b/src/connector/src/source/cdc/external/postgres.rs index 9df4b32e3f5ec..0bbf5dccbe6de 100644 --- a/src/connector/src/source/cdc/external/postgres.rs +++ b/src/connector/src/source/cdc/external/postgres.rs @@ -15,23 +15,29 @@ use std::cmp::Ordering; use std::collections::HashMap; -use anyhow::Context; +use anyhow::{anyhow, Context}; use futures::stream::BoxStream; use futures::{pin_mut, StreamExt}; use futures_async_stream::try_stream; use itertools::Itertools; use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; use postgres_openssl::MakeTlsConnector; -use risingwave_common::catalog::Schema; +use risingwave_common::catalog::{ColumnDesc, ColumnId, Schema}; use risingwave_common::row::{OwnedRow, Row}; -use risingwave_common::types::DatumRef; +use risingwave_common::types::{DataType, StructType}; +use risingwave_common::util::iter_util::ZipEqFast; +use sea_schema::postgres::def::{ColumnType, TableInfo}; +use sea_schema::postgres::discovery::SchemaDiscovery; use serde_derive::{Deserialize, Serialize}; +use sqlx::postgres::{PgConnectOptions, PgSslMode}; +use sqlx::PgPool; use thiserror_ext::AsReport; use tokio_postgres::types::PgLsn; -use tokio_postgres::NoTls; +use tokio_postgres::{NoTls, Statement}; use crate::error::{ConnectorError, ConnectorResult}; use crate::parser::postgres_row_to_owned_row; +use crate::parser::scalar_adapter::ScalarAdapter; #[cfg(not(madsim))] use crate::source::cdc::external::maybe_tls_connector::MaybeMakeTlsConnector; use crate::source::cdc::external::{ @@ -72,23 +78,165 @@ impl PostgresOffset { } } -#[derive(Debug)] +pub struct PostgresExternalTable { + column_descs: Vec, + pk_names: Vec, +} + +impl PostgresExternalTable { + pub async fn connect(config: ExternalTableConfig) -> ConnectorResult { + tracing::debug!("connect to postgres external table"); + let options = PgConnectOptions::new() + .username(&config.username) + .password(&config.password) + .host(&config.host) + .port(config.port.parse::().unwrap()) + .database(&config.database) + .ssl_mode(match config.sslmode { + SslMode::Disabled => PgSslMode::Disable, + SslMode::Preferred => PgSslMode::Prefer, + SslMode::Required => PgSslMode::Require, + }); + + let connection = PgPool::connect_with(options).await?; + let schema_discovery = SchemaDiscovery::new(connection, config.schema.as_str()); + // fetch column schema and primary key + let empty_map = HashMap::new(); + let table_schema = schema_discovery + .discover_table( + TableInfo { + name: config.table.clone(), + of_type: None, + }, + &empty_map, + ) + .await?; + + let mut column_descs = vec![]; + for col in &table_schema.columns { + let data_type = type_to_rw_type(&col.col_type)?; + column_descs.push(ColumnDesc::named( + col.name.clone(), + ColumnId::placeholder(), + data_type, + )); + } + + if table_schema.primary_key_constraints.is_empty() { + return Err(anyhow!("Postgres table doesn't define the primary key").into()); + } + let mut pk_names = vec![]; + table_schema.primary_key_constraints.iter().for_each(|pk| { + pk_names.extend(pk.columns.clone()); + }); + + Ok(Self { + column_descs, + pk_names, + }) + } + + pub fn column_descs(&self) -> &Vec { + &self.column_descs + } + + pub fn pk_names(&self) -> &Vec { + &self.pk_names + } +} + +fn type_to_rw_type(col_type: &ColumnType) -> ConnectorResult { + let dtype = match col_type { + ColumnType::SmallInt | ColumnType::SmallSerial => DataType::Int16, + ColumnType::Integer | ColumnType::Serial => DataType::Int32, + ColumnType::BigInt | ColumnType::BigSerial => DataType::Int64, + ColumnType::Money | ColumnType::Decimal(_) | ColumnType::Numeric(_) => DataType::Decimal, + ColumnType::Real => DataType::Float32, + ColumnType::DoublePrecision => DataType::Float64, + ColumnType::Varchar(_) | ColumnType::Char(_) | ColumnType::Text => DataType::Varchar, + ColumnType::Bytea => DataType::Bytea, + ColumnType::Timestamp(_) => DataType::Timestamp, + ColumnType::TimestampWithTimeZone(_) => DataType::Timestamptz, + ColumnType::Date => DataType::Date, + ColumnType::Time(_) | ColumnType::TimeWithTimeZone(_) => DataType::Time, + ColumnType::Interval(_) => DataType::Interval, + ColumnType::Boolean => DataType::Boolean, + ColumnType::Point => DataType::Struct(StructType::new(vec![ + ("x", DataType::Float32), + ("y", DataType::Float32), + ])), + ColumnType::Uuid => DataType::Varchar, + ColumnType::Xml => DataType::Varchar, + ColumnType::Json => DataType::Jsonb, + ColumnType::JsonBinary => DataType::Jsonb, + ColumnType::Array(def) => { + let item_type = match def.col_type.as_ref() { + Some(ty) => type_to_rw_type(ty.as_ref())?, + None => { + return Err(anyhow!("ARRAY type missing element type").into()); + } + }; + + DataType::List(Box::new(item_type)) + } + ColumnType::PgLsn => DataType::Int64, + ColumnType::Cidr + | ColumnType::Inet + | ColumnType::MacAddr + | ColumnType::MacAddr8 + | ColumnType::Int4Range + | ColumnType::Int8Range + | ColumnType::NumRange + | ColumnType::TsRange + | ColumnType::TsTzRange + | ColumnType::DateRange + | ColumnType::Enum(_) => DataType::Varchar, + + ColumnType::Line => { + return Err(anyhow!("LINE type not supported").into()); + } + ColumnType::Lseg => { + return Err(anyhow!("LSEG type not supported").into()); + } + ColumnType::Box => { + return Err(anyhow!("BOX type not supported").into()); + } + ColumnType::Path => { + return Err(anyhow!("PATH type not supported").into()); + } + ColumnType::Polygon => { + return Err(anyhow!("POLYGON type not supported").into()); + } + ColumnType::Circle => { + return Err(anyhow!("CIRCLE type not supported").into()); + } + ColumnType::Bit(_) => { + return Err(anyhow!("BIT type not supported").into()); + } + ColumnType::TsVector => { + return Err(anyhow!("TSVECTOR type not supported").into()); + } + ColumnType::TsQuery => { + return Err(anyhow!("TSQUERY type not supported").into()); + } + ColumnType::Unknown(name) => { + // NOTES: user-defined enum type is classified as `Unknown` + tracing::warn!("Unknown Postgres data type: {name}, map to varchar"); + DataType::Varchar + } + }; + + Ok(dtype) +} + pub struct PostgresExternalTableReader { - config: ExternalTableConfig, rw_schema: Schema, field_names: String, - + prepared_scan_stmt: Statement, client: tokio::sync::Mutex, } impl ExternalTableReader for PostgresExternalTableReader { - fn get_normalized_table_name(&self, table_name: &SchemaTableName) -> String { - format!( - "\"{}\".\"{}\"", - table_name.schema_name, table_name.table_name - ) - } - async fn current_cdc_offset(&self) -> ConnectorResult { let mut client = self.client.lock().await; // start a transaction to read current lsn and txid @@ -122,41 +270,47 @@ impl ExternalTableReader for PostgresExternalTableReader { impl PostgresExternalTableReader { pub async fn new( - properties: HashMap, + config: ExternalTableConfig, rw_schema: Schema, + pk_indices: Vec, + scan_limit: u32, ) -> ConnectorResult { - tracing::debug!(?rw_schema, "create postgres external table reader"); - - let config = serde_json::from_value::( - serde_json::to_value(properties).unwrap(), - ) - .context("failed to extract postgres connector properties")?; - - let database_url = format!( - "postgresql://{}:{}@{}:{}/{}?sslmode={}", - config.username, - config.password, - config.host, - config.port, - config.database, - config.sslmode + tracing::info!( + ?rw_schema, + ?pk_indices, + "create postgres external table reader" ); + let mut pg_config = tokio_postgres::Config::new(); + pg_config + .user(&config.username) + .password(&config.password) + .host(&config.host) + .port(config.port.parse::().unwrap()) + .dbname(&config.database); + #[cfg(not(madsim))] let connector = match config.sslmode { - SslMode::Disable => MaybeMakeTlsConnector::NoTls(NoTls), - SslMode::Prefer => match SslConnector::builder(SslMethod::tls()) { - Ok(mut builder) => { - // disable certificate verification for `prefer` - builder.set_verify(SslVerifyMode::NONE); - MaybeMakeTlsConnector::Tls(MakeTlsConnector::new(builder.build())) - } - Err(e) => { - tracing::warn!(error = %e.as_report(), "SSL connector error"); - MaybeMakeTlsConnector::NoTls(NoTls) + SslMode::Disabled => { + pg_config.ssl_mode(tokio_postgres::config::SslMode::Disable); + MaybeMakeTlsConnector::NoTls(NoTls) + } + SslMode::Preferred => { + pg_config.ssl_mode(tokio_postgres::config::SslMode::Prefer); + match SslConnector::builder(SslMethod::tls()) { + Ok(mut builder) => { + // disable certificate verification for `prefer` + builder.set_verify(SslVerifyMode::NONE); + MaybeMakeTlsConnector::Tls(MakeTlsConnector::new(builder.build())) + } + Err(e) => { + tracing::warn!(error = %e.as_report(), "SSL connector error"); + MaybeMakeTlsConnector::NoTls(NoTls) + } } - }, - SslMode::Require => { + } + SslMode::Required => { + pg_config.ssl_mode(tokio_postgres::config::SslMode::Require); let mut builder = SslConnector::builder(SslMethod::tls())?; // disable certificate verification for `require` builder.set_verify(SslVerifyMode::NONE); @@ -166,7 +320,7 @@ impl PostgresExternalTableReader { #[cfg(madsim)] let connector = NoTls; - let (client, connection) = tokio_postgres::connect(&database_url, connector).await?; + let (client, connection) = pg_config.connect(connector).await?; tokio::spawn(async move { if let Err(e) = connection.await { @@ -180,14 +334,43 @@ impl PostgresExternalTableReader { .map(|f| Self::quote_column(&f.name)) .join(","); + // prepare once + let prepared_scan_stmt = { + let primary_keys = pk_indices + .iter() + .map(|i| rw_schema.fields[*i].name.clone()) + .collect_vec(); + + let table_name = SchemaTableName { + schema_name: config.schema.clone(), + table_name: config.table.clone(), + }; + let order_key = Self::get_order_key(&primary_keys); + let scan_sql = format!( + "SELECT {} FROM {} WHERE {} ORDER BY {} LIMIT {scan_limit}", + field_names, + Self::get_normalized_table_name(&table_name), + Self::filter_expression(&primary_keys), + order_key, + ); + client.prepare(&scan_sql).await? + }; + Ok(Self { - config, rw_schema, field_names, + prepared_scan_stmt, client: tokio::sync::Mutex::new(client), }) } + pub fn get_normalized_table_name(table_name: &SchemaTableName) -> String { + format!( + "\"{}\".\"{}\"", + table_name.schema_name, table_name.table_name + ) + } + pub fn get_cdc_offset_parser() -> CdcOffsetParseFunc { Box::new(move |offset| { Ok(CdcOffset::Postgres(PostgresOffset::parse_debezium_offset( @@ -204,34 +387,36 @@ impl PostgresExternalTableReader { primary_keys: Vec, limit: u32, ) { - let order_key = primary_keys.iter().join(","); - let sql = if start_pk_row.is_none() { - format!( - "SELECT {} FROM {} ORDER BY {} LIMIT {limit}", - self.field_names, - self.get_normalized_table_name(&table_name), - order_key, - ) - } else { - let filter_expr = Self::filter_expression(&primary_keys); - format!( - "SELECT {} FROM {} WHERE {} ORDER BY {} LIMIT {limit}", - self.field_names, - self.get_normalized_table_name(&table_name), - filter_expr, - order_key, - ) - }; - + let order_key = Self::get_order_key(&primary_keys); let client = self.client.lock().await; client.execute("set time zone '+00:00'", &[]).await?; - let params: Vec> = match start_pk_row { - Some(ref pk_row) => pk_row.iter().collect_vec(), - None => Vec::new(), + let stream = match start_pk_row { + Some(ref pk_row) => { + let params: Vec> = pk_row + .iter() + .zip_eq_fast(self.prepared_scan_stmt.params()) + .map(|(datum, ty)| { + datum + .map(|scalar| ScalarAdapter::from_scalar(scalar, ty)) + .transpose() + }) + .try_collect()?; + + client.query_raw(&self.prepared_scan_stmt, ¶ms).await? + } + None => { + let sql = format!( + "SELECT {} FROM {} ORDER BY {} LIMIT {limit}", + self.field_names, + Self::get_normalized_table_name(&table_name), + order_key, + ); + let params: Vec> = vec![]; + client.query_raw(&sql, ¶ms).await? + } }; - let stream = client.query_raw(&sql, ¶ms).await?; let row_stream = stream.map(|row| { let row = row?; Ok::<_, crate::error::ConnectorError>(postgres_row_to_owned_row(row, &self.rw_schema)) @@ -260,6 +445,13 @@ impl PostgresExternalTableReader { format!("({}) > ({})", col_expr, arg_expr) } + fn get_order_key(primary_keys: &Vec) -> String { + primary_keys + .iter() + .map(|col| Self::quote_column(col)) + .join(",") + } + fn quote_column(column: &str) -> String { format!("\"{}\"", column) } @@ -267,6 +459,9 @@ impl PostgresExternalTableReader { #[cfg(test)] mod tests { + + use std::collections::HashMap; + use futures::pin_mut; use futures_async_stream::for_await; use maplit::{convert_args, hashmap}; @@ -274,8 +469,31 @@ mod tests { use risingwave_common::row::OwnedRow; use risingwave_common::types::{DataType, ScalarImpl}; - use crate::source::cdc::external::postgres::{PostgresExternalTableReader, PostgresOffset}; - use crate::source::cdc::external::{ExternalTableReader, SchemaTableName}; + use crate::source::cdc::external::postgres::{ + PostgresExternalTable, PostgresExternalTableReader, PostgresOffset, + }; + use crate::source::cdc::external::{ExternalTableConfig, ExternalTableReader, SchemaTableName}; + + #[ignore] + #[tokio::test] + async fn test_postgres_schema() { + let config = ExternalTableConfig { + connector: "postgres-cdc".to_string(), + host: "localhost".to_string(), + port: "8432".to_string(), + username: "myuser".to_string(), + password: "123456".to_string(), + database: "mydb".to_string(), + schema: "public".to_string(), + table: "mytest".to_string(), + sslmode: Default::default(), + }; + + let table = PostgresExternalTable::connect(config).await.unwrap(); + + println!("columns: {:?}", &table.column_descs); + println!("primary keys: {:?}", &table.pk_names); + } #[test] fn test_postgres_offset() { @@ -317,7 +535,7 @@ mod tests { fields: columns.iter().map(Field::from).collect(), }; - let props = convert_args!(hashmap!( + let props: HashMap = convert_args!(hashmap!( "hostname" => "localhost", "port" => "8432", "username" => "myuser", @@ -325,7 +543,11 @@ mod tests { "database.name" => "mydb", "schema.name" => "public", "table.name" => "t1")); - let reader = PostgresExternalTableReader::new(props, rw_schema) + + let config = + serde_json::from_value::(serde_json::to_value(props).unwrap()) + .unwrap(); + let reader = PostgresExternalTableReader::new(config, rw_schema, vec![0, 1], 1000) .await .unwrap(); diff --git a/src/connector/src/source/cdc/mod.rs b/src/connector/src/source/cdc/mod.rs index decbcffebc36d..2a4200b5cc4a0 100644 --- a/src/connector/src/source/cdc/mod.rs +++ b/src/connector/src/source/cdc/mod.rs @@ -18,20 +18,20 @@ pub mod jni_source; pub mod source; pub mod split; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::marker::PhantomData; pub use enumerator::*; use itertools::Itertools; -use risingwave_common::catalog::{ColumnDesc, Field, Schema}; use risingwave_pb::catalog::PbSource; use risingwave_pb::connector_service::{PbSourceType, PbTableSchema, SourceType, TableSchema}; +use risingwave_pb::plan_common::column_desc::GeneratedOrDefaultColumn; use risingwave_pb::plan_common::ExternalTableDesc; use simd_json::prelude::ArrayTrait; pub use source::*; use crate::error::ConnectorResult; -use crate::source::{SourceProperties, SplitImpl, TryFromHashmap}; +use crate::source::{SourceProperties, SplitImpl, TryFromBTreeMap}; use crate::{for_all_classified_sources, impl_cdc_source_type}; pub const CDC_CONNECTOR_NAME_SUFFIX: &str = "-cdc"; @@ -40,8 +40,11 @@ pub const CDC_SNAPSHOT_BACKFILL: &str = "rw_cdc_backfill"; pub const CDC_SHARING_MODE_KEY: &str = "rw.sharing.mode.enable"; // User can set snapshot='false' to disable cdc backfill pub const CDC_BACKFILL_ENABLE_KEY: &str = "snapshot"; +pub const CDC_BACKFILL_SNAPSHOT_INTERVAL_KEY: &str = "snapshot.interval"; +pub const CDC_BACKFILL_SNAPSHOT_BATCH_SIZE_KEY: &str = "snapshot.batch_size"; // We enable transaction for shared cdc source by default pub const CDC_TRANSACTIONAL_KEY: &str = "transactional"; +pub const CDC_WAIT_FOR_STREAMING_START_TIMEOUT: &str = "cdc.source.wait.streaming.start.timeout"; pub const MYSQL_CDC_CONNECTOR: &str = Mysql::CDC_CONNECTOR_NAME; pub const POSTGRES_CDC_CONNECTOR: &str = Postgres::CDC_CONNECTOR_NAME; @@ -82,7 +85,7 @@ impl CdcSourceType { #[derive(Clone, Debug, Default)] pub struct CdcProperties { /// Properties specified in the WITH clause by user - pub properties: HashMap, + pub properties: BTreeMap, /// Schema of the source specified by users pub table_schema: TableSchema, @@ -96,9 +99,25 @@ pub struct CdcProperties { pub _phantom: PhantomData, } -impl TryFromHashmap for CdcProperties { - fn try_from_hashmap( - properties: HashMap, +pub fn table_schema_exclude_additional_columns(table_schema: &TableSchema) -> TableSchema { + TableSchema { + columns: table_schema + .columns + .iter() + .filter(|col| { + col.additional_column + .as_ref() + .is_some_and(|val| val.column_type.is_none()) + }) + .cloned() + .collect(), + pk_indices: table_schema.pk_indices.clone(), + } +} + +impl TryFromBTreeMap for CdcProperties { + fn try_from_btreemap( + properties: BTreeMap, _deny_unknown_fields: bool, ) -> ConnectorResult { let is_share_source = properties @@ -144,6 +163,12 @@ where .columns .iter() .flat_map(|col| &col.column_desc) + .filter(|col| { + !matches!( + col.generated_or_default_column, + Some(GeneratedOrDefaultColumn::GeneratedColumn(_)) + ) + }) .cloned() .collect(), pk_indices, @@ -155,11 +180,20 @@ where } fn init_from_pb_cdc_table_desc(&mut self, table_desc: &ExternalTableDesc) { - let properties: HashMap = - table_desc.connect_properties.clone().into_iter().collect(); + let properties = table_desc.connect_properties.clone(); let table_schema = TableSchema { - columns: table_desc.columns.clone(), + columns: table_desc + .columns + .iter() + .filter(|col| { + !matches!( + col.generated_or_default_column, + Some(GeneratedOrDefaultColumn::GeneratedColumn(_)) + ) + }) + .cloned() + .collect(), pk_indices: table_desc.stream_key.clone(), }; @@ -181,16 +215,4 @@ impl CdcProperties { pub fn get_source_type_pb(&self) -> SourceType { SourceType::from(T::source_type()) } - - pub fn schema(&self) -> Schema { - Schema { - fields: self - .table_schema - .columns - .iter() - .map(ColumnDesc::from) - .map(Field::from) - .collect(), - } - } } diff --git a/src/connector/src/source/cdc/source/message.rs b/src/connector/src/source/cdc/source/message.rs index 5df937a83fbe8..f12d18339b527 100644 --- a/src/connector/src/source/cdc/source/message.rs +++ b/src/connector/src/source/cdc/source/message.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use risingwave_common::types::{DatumRef, ScalarRefImpl, Timestamptz}; use risingwave_pb::connector_service::CdcMessage; use crate::source::base::SourceMessage; @@ -19,6 +20,8 @@ use crate::source::SourceMeta; #[derive(Debug, Clone)] pub struct DebeziumCdcMeta { + db_name_prefix_len: usize, + pub full_table_name: String, // extracted from `payload.source.ts_ms`, the time that the change event was made in the database pub source_ts_ms: i64, @@ -26,6 +29,37 @@ pub struct DebeziumCdcMeta { pub is_transaction_meta: bool, } +impl DebeziumCdcMeta { + pub fn extract_timestamp(&self) -> DatumRef<'_> { + Some(ScalarRefImpl::Timestamptz( + Timestamptz::from_millis(self.source_ts_ms).unwrap(), + )) + } + + pub fn extract_database_name(&self) -> DatumRef<'_> { + Some(ScalarRefImpl::Utf8( + &self.full_table_name.as_str()[0..self.db_name_prefix_len], + )) + } + + pub fn extract_table_name(&self) -> DatumRef<'_> { + Some(ScalarRefImpl::Utf8( + &self.full_table_name.as_str()[self.db_name_prefix_len..], + )) + } + + pub fn new(full_table_name: String, source_ts_ms: i64, is_transaction_meta: bool) -> Self { + // full_table_name is in the format of `database_name.table_name` + let db_name_prefix_len = full_table_name.as_str().find('.').unwrap_or(0); + Self { + db_name_prefix_len, + full_table_name, + source_ts_ms, + is_transaction_meta, + } + } +} + impl From for SourceMessage { fn from(message: CdcMessage) -> Self { SourceMessage { @@ -41,11 +75,11 @@ impl From for SourceMessage { }, offset: message.offset, split_id: message.partition.into(), - meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta { - full_table_name: message.full_table_name, - source_ts_ms: message.source_ts_ms, - is_transaction_meta: message.is_transaction_meta, - }), + meta: SourceMeta::DebeziumCdc(DebeziumCdcMeta::new( + message.full_table_name, + message.source_ts_ms, + message.is_transaction_meta, + )), } } } diff --git a/src/connector/src/source/cdc/source/reader.rs b/src/connector/src/source/cdc/source/reader.rs index cf2b5c3d17e00..171b25bc94782 100644 --- a/src/connector/src/source/cdc/source/reader.rs +++ b/src/connector/src/source/cdc/source/reader.rs @@ -22,7 +22,7 @@ use prost::Message; use risingwave_common::bail; use risingwave_common::metrics::GLOBAL_ERROR_METRICS; use risingwave_common::util::addr::HostAddr; -use risingwave_jni_core::jvm_runtime::JVM; +use risingwave_jni_core::jvm_runtime::{execute_with_jni_env, JVM}; use risingwave_jni_core::{call_static_method, JniReceiverType, JniSenderType}; use risingwave_pb::connector_service::{GetEventStreamRequest, GetEventStreamResponse}; use thiserror_ext::AsReport; @@ -33,19 +33,23 @@ use crate::parser::ParserConfig; use crate::source::base::SourceMessage; use crate::source::cdc::{CdcProperties, CdcSourceType, CdcSourceTypeTrait, DebeziumCdcSplit}; use crate::source::{ - into_chunk_stream, BoxChunkSourceStream, Column, CommonSplitReader, SourceContextRef, SplitId, - SplitMetaData, SplitReader, + into_chunk_stream, BoxChunkSourceStream, Column, SourceContextRef, SplitId, SplitMetaData, + SplitReader, }; pub struct CdcSplitReader { source_id: u64, + #[expect(dead_code)] start_offset: Option, // host address of worker node for a Citus cluster + #[expect(dead_code)] server_addr: Option, + #[expect(dead_code)] conn_props: CdcProperties, - + #[expect(dead_code)] split_id: SplitId, // whether the full snapshot phase is done + #[expect(dead_code)] snapshot_done: bool, parser_config: ParserConfig, source_ctx: SourceContextRef, @@ -107,38 +111,43 @@ impl SplitReader for CdcSplitReader { }; std::thread::spawn(move || { - let result: anyhow::Result<_> = try { - let env = jvm.attach_current_thread()?; - let get_event_stream_request_bytes = - env.byte_array_from_slice(&Message::encode_to_vec(&get_event_stream_request))?; - (env, get_event_stream_request_bytes) - }; - - let (mut env, get_event_stream_request_bytes) = match result { - Ok(inner) => inner, - Err(e) => { - let _ = tx - .blocking_send(Err(e.context("err before calling runJniDbzSourceThread"))); - return; + execute_with_jni_env(jvm, |env| { + let result: anyhow::Result<_> = try { + let get_event_stream_request_bytes = env.byte_array_from_slice( + &Message::encode_to_vec(&get_event_stream_request), + )?; + (env, get_event_stream_request_bytes) + }; + + let (env, get_event_stream_request_bytes) = match result { + Ok(inner) => inner, + Err(e) => { + let _ = tx.blocking_send(Err( + e.context("err before calling runJniDbzSourceThread") + )); + return Ok(()); + } + }; + + let result = call_static_method!( + env, + {com.risingwave.connector.source.core.JniDbzSourceHandler}, + {void runJniDbzSourceThread(byte[] getEventStreamRequestBytes, long channelPtr)}, + &get_event_stream_request_bytes, + &mut tx as *mut JniSenderType + ); + + match result { + Ok(_) => { + tracing::info!(?source_id, "end of jni call runJniDbzSourceThread"); + } + Err(e) => { + tracing::error!(?source_id, error = %e.as_report(), "jni call error"); + } } - }; - - let result = call_static_method!( - env, - {com.risingwave.connector.source.core.JniDbzSourceHandler}, - {void runJniDbzSourceThread(byte[] getEventStreamRequestBytes, long channelPtr)}, - &get_event_stream_request_bytes, - &mut tx as *mut JniSenderType - ); - match result { - Ok(_) => { - tracing::info!(?source_id, "end of jni call runJniDbzSourceThread"); - } - Err(e) => { - tracing::error!(?source_id, error = %e.as_report(), "jni call error"); - } - } + Ok(()) + }) }); // wait for the handshake message @@ -190,11 +199,11 @@ impl SplitReader for CdcSplitReader { fn into_stream(self) -> BoxChunkSourceStream { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } -impl CommonSplitReader for CdcSplitReader { +impl CdcSplitReader { #[try_stream(ok = Vec, error = ConnectorError)] async fn into_data_stream(self) { let source_type = T::source_type(); diff --git a/src/connector/src/source/common.rs b/src/connector/src/source/common.rs index 8fcd1318401f2..abb80221e8fae 100644 --- a/src/connector/src/source/common.rs +++ b/src/connector/src/source/common.rs @@ -18,15 +18,13 @@ use risingwave_common::array::StreamChunk; use crate::error::{ConnectorError, ConnectorResult}; use crate::parser::ParserConfig; -use crate::source::{SourceContextRef, SourceMessage, SplitReader}; - -pub(crate) trait CommonSplitReader: SplitReader + 'static { - fn into_data_stream(self) -> impl Stream>> + Send; -} +use crate::source::{SourceContextRef, SourceMessage}; +/// Utility function to convert [`SourceMessage`] stream (got from specific connector's [`SplitReader`](super::SplitReader)) +/// into [`StreamChunk`] stream (by invoking [`ByteStreamSourceParserImpl`](crate::parser::ByteStreamSourceParserImpl)). #[try_stream(boxed, ok = StreamChunk, error = ConnectorError)] pub(crate) async fn into_chunk_stream( - reader: impl CommonSplitReader, + data_stream: impl Stream>> + Send + 'static, parser_config: ParserConfig, source_ctx: SourceContextRef, ) { @@ -36,8 +34,7 @@ pub(crate) async fn into_chunk_stream( let source_name = source_ctx.source_name.to_string(); let metrics = source_ctx.metrics.clone(); - let data_stream = reader.into_data_stream(); - + // add metrics to the data stream let data_stream = data_stream .inspect_ok(move |data_batch| { let mut by_split_id = std::collections::HashMap::new(); diff --git a/src/connector/src/source/datagen/source/generator.rs b/src/connector/src/source/datagen/source/generator.rs index 5716631dff620..600efda2f6255 100644 --- a/src/connector/src/source/datagen/source/generator.rs +++ b/src/connector/src/source/datagen/source/generator.rs @@ -278,7 +278,6 @@ mod tests { use_schema_registry: false, timestamptz_handling: None, }), - key_encoding_config: None, }, data_types, rows_per_second, diff --git a/src/connector/src/source/datagen/source/reader.rs b/src/connector/src/source/datagen/source/reader.rs index 87f798d59f38b..992343058e9ef 100644 --- a/src/connector/src/source/datagen/source/reader.rs +++ b/src/connector/src/source/datagen/source/reader.rs @@ -27,12 +27,13 @@ use crate::source::data_gen_util::spawn_data_generation_stream; use crate::source::datagen::source::SEQUENCE_FIELD_KIND; use crate::source::datagen::{DatagenProperties, DatagenSplit, FieldDesc}; use crate::source::{ - into_chunk_stream, BoxChunkSourceStream, Column, CommonSplitReader, DataType, SourceContextRef, - SourceMessage, SplitId, SplitMetaData, SplitReader, + into_chunk_stream, BoxChunkSourceStream, Column, DataType, SourceContextRef, SourceMessage, + SplitId, SplitMetaData, SplitReader, }; pub struct DatagenSplitReader { generator: DatagenEventGenerator, + #[expect(dead_code)] assigned_split: DatagenSplit, split_id: SplitId, @@ -177,13 +178,13 @@ impl SplitReader for DatagenSplitReader { _ => { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } } } -impl CommonSplitReader for DatagenSplitReader { +impl DatagenSplitReader { fn into_data_stream(self) -> impl Stream>> { // Will buffer at most 4 event chunks. const BUFFER_SIZE: usize = 4; @@ -398,7 +399,6 @@ mod tests { state, ParserConfig { specific: SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Native, protocol_config: ProtocolProperties::Native, }, @@ -456,7 +456,6 @@ mod tests { }; let parser_config = ParserConfig { specific: SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Native, protocol_config: ProtocolProperties::Native, }, diff --git a/src/connector/src/source/filesystem/file_common.rs b/src/connector/src/source/filesystem/file_common.rs index b2c5d5ccdb4da..e012fc9ce1e1b 100644 --- a/src/connector/src/source/filesystem/file_common.rs +++ b/src/connector/src/source/filesystem/file_common.rs @@ -18,6 +18,7 @@ use std::marker::PhantomData; use aws_sdk_s3::types::Object; use risingwave_common::types::{JsonbVal, Timestamptz}; use serde::{Deserialize, Serialize}; +use strum::Display; use super::opendal_source::OpendalSource; use crate::error::ConnectorResult; @@ -141,3 +142,12 @@ pub struct FsPageItem { } pub type FsPage = Vec; + +#[derive(Debug, Default, Clone, PartialEq, Display, Deserialize)] +pub enum CompressionFormat { + #[default] + None, + + #[serde(rename = "gzip", alias = "gz")] + Gzip, +} diff --git a/src/connector/src/source/filesystem/opendal_source/gcs_source.rs b/src/connector/src/source/filesystem/opendal_source/gcs_source.rs index 01594af4e4bad..768f19fc36722 100644 --- a/src/connector/src/source/filesystem/opendal_source/gcs_source.rs +++ b/src/connector/src/source/filesystem/opendal_source/gcs_source.rs @@ -58,11 +58,15 @@ impl OpendalEnumerator { } else { (None, None) }; + + let compression_format = gcs_properties.compression_format; + Ok(Self { op, prefix, matcher, marker: PhantomData, + compression_format, }) } } diff --git a/src/connector/src/source/filesystem/opendal_source/mod.rs b/src/connector/src/source/filesystem/opendal_source/mod.rs index a9689a921d7f0..26a311f26eb8d 100644 --- a/src/connector/src/source/filesystem/opendal_source/mod.rs +++ b/src/connector/src/source/filesystem/opendal_source/mod.rs @@ -25,6 +25,7 @@ pub mod opendal_reader; use self::opendal_enumerator::OpendalEnumerator; use self::opendal_reader::OpendalReader; +use super::file_common::CompressionFormat; use super::s3::S3PropertiesCommon; use super::OpendalFsSplit; use crate::error::ConnectorResult; @@ -53,6 +54,9 @@ pub struct GcsProperties { #[serde(flatten)] pub unknown_fields: HashMap, + + #[serde(rename = "compression_format", default = "Default::default")] + pub compression_format: CompressionFormat, } impl UnknownFields for GcsProperties { @@ -147,6 +151,8 @@ pub struct PosixFsProperties { #[serde(flatten)] pub unknown_fields: HashMap, + #[serde(rename = "compression_format", default = "Default::default")] + pub compression_format: CompressionFormat, } impl UnknownFields for PosixFsProperties { diff --git a/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs b/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs index 96646ade0e1df..864d1de56c7be 100644 --- a/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs +++ b/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs @@ -23,6 +23,7 @@ use risingwave_common::types::Timestamptz; use super::OpendalSource; use crate::error::ConnectorResult; +use crate::source::filesystem::file_common::CompressionFormat; use crate::source::filesystem::{FsPageItem, OpendalFsSplit}; use crate::source::{SourceEnumeratorContextRef, SplitEnumerator}; @@ -33,6 +34,7 @@ pub struct OpendalEnumerator { pub(crate) prefix: Option, pub(crate) matcher: Option, pub(crate) marker: PhantomData, + pub(crate) compression_format: CompressionFormat, } #[async_trait] @@ -56,10 +58,7 @@ impl SplitEnumerator for OpendalEnumerator { impl OpendalEnumerator { pub async fn list(&self) -> ConnectorResult { - let prefix = match &self.prefix { - Some(prefix) => prefix, - None => "", - }; + let prefix = self.prefix.as_deref().unwrap_or(""); let object_lister = self .op diff --git a/src/connector/src/source/filesystem/opendal_source/opendal_reader.rs b/src/connector/src/source/filesystem/opendal_source/opendal_reader.rs index c6a3768d4f9b8..0be687084a756 100644 --- a/src/connector/src/source/filesystem/opendal_source/opendal_reader.rs +++ b/src/connector/src/source/filesystem/opendal_source/opendal_reader.rs @@ -13,29 +13,32 @@ // limitations under the License. use std::future::IntoFuture; +use std::pin::Pin; +use async_compression::tokio::bufread::GzipDecoder; use async_trait::async_trait; use futures::TryStreamExt; use futures_async_stream::try_stream; use opendal::Reader; use parquet::arrow::ParquetRecordBatchStreamBuilder; use risingwave_common::array::StreamChunk; -use tokio::io::BufReader; +use tokio::io::{AsyncRead, BufReader}; use tokio_util::io::{ReaderStream, StreamReader}; use super::opendal_enumerator::OpendalEnumerator; use super::OpendalSource; use crate::error::ConnectorResult; use crate::parser::{ByteStreamSourceParserImpl, EncodingProperties, ParquetParser, ParserConfig}; +use crate::source::filesystem::file_common::CompressionFormat; use crate::source::filesystem::nd_streaming::need_nd_streaming; use crate::source::filesystem::{nd_streaming, OpendalFsSplit}; use crate::source::{ - BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SourceMeta, SplitMetaData, - SplitReader, + into_chunk_stream, BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SourceMeta, + SplitMetaData, SplitReader, }; -const MAX_CHANNEL_BUFFER_SIZE: usize = 2048; const STREAM_READER_CAPACITY: usize = 4096; + #[derive(Debug, Clone)] pub struct OpendalReader { connector: OpendalEnumerator, @@ -68,13 +71,13 @@ impl SplitReader for OpendalReader { } fn into_stream(self) -> BoxChunkSourceStream { - self.into_chunk_stream() + self.into_stream_inner() } } impl OpendalReader { #[try_stream(boxed, ok = StreamChunk, error = crate::error::ConnectorError)] - async fn into_chunk_stream(self) { + async fn into_stream_inner(self) { let actor_id = self.source_ctx.actor_id.to_string(); let fragment_id = self.source_ctx.fragment_id.to_string(); let source_id = self.source_ctx.source_id.to_string(); @@ -87,9 +90,10 @@ impl OpendalReader { .connector .op .reader_with(&split.name.clone()) - .range(split.offset as u64..) - .into_future() // Unlike `rustc`, `try_stream` seems require manual `into_future`. - .await?; + .await? + .into_futures_async_read(split.offset as u64..) .await?; + let object_name = split.name.clone(); + let msg_stream; if let EncodingProperties::Parquet = &self.parser_config.specific.encoding_config { @@ -104,8 +108,12 @@ impl OpendalReader { ParquetParser::new(self.parser_config.common.rw_columns.clone(), source_ctx)?; msg_stream = parquet_parser.into_stream(record_batch_stream, split.name.clone()); } else { - let data_stream = - Self::stream_read_object(file_reader, split, self.source_ctx.clone()); + let data_stream = Self::stream_read_object( + file_reader, + split, + self.source_ctx.clone(), + self.connector.compression_format, + ); let parser = ByteStreamSourceParserImpl::create(self.parser_config.clone(), source_ctx) @@ -119,17 +127,6 @@ impl OpendalReader { #[for_await] for msg in msg_stream { let msg = msg?; - self.source_ctx - .metrics - .partition_input_count - .with_label_values(&[ - &actor_id, - &source_id, - &split_id, - &source_name, - &fragment_id, - ]) - .inc_by(msg.cardinality() as u64); yield msg; } } @@ -140,6 +137,7 @@ impl OpendalReader { file_reader: Reader, split: OpendalFsSplit, source_ctx: SourceContextRef, + compression_format: CompressionFormat, ) { let actor_id = source_ctx.actor_id.to_string(); let fragment_id = source_ctx.fragment_id.to_string(); @@ -147,16 +145,31 @@ impl OpendalReader { let source_name = source_ctx.source_name.to_string(); let max_chunk_size = source_ctx.source_ctrl_opts.chunk_size; let split_id = split.id(); - + let object_name = split.name.clone(); let stream_reader = StreamReader::new( file_reader.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)), ); - let buf_reader = BufReader::new(stream_reader); - let stream = ReaderStream::with_capacity(buf_reader, STREAM_READER_CAPACITY); + + let buf_reader: Pin> = match compression_format { + CompressionFormat::Gzip => { + let gzip_decoder = GzipDecoder::new(stream_reader); + Box::pin(BufReader::new(gzip_decoder)) as Pin> + } + CompressionFormat::None => { + // todo: support automatic decompression of more compression types. + if object_name.ends_with(".gz") || object_name.ends_with(".gzip") { + let gzip_decoder = GzipDecoder::new(stream_reader); + Box::pin(BufReader::new(gzip_decoder)) as Pin> + } else { + Box::pin(BufReader::new(stream_reader)) as Pin> + } + } + }; let mut offset: usize = split.offset; let mut batch_size: usize = 0; let mut batch = Vec::new(); + let stream = ReaderStream::with_capacity(buf_reader, STREAM_READER_CAPACITY); #[for_await] for read in stream { let bytes = read?; diff --git a/src/connector/src/source/filesystem/opendal_source/posix_fs_source.rs b/src/connector/src/source/filesystem/opendal_source/posix_fs_source.rs index 3a4fb7fcfeaa7..a7a984da663c3 100644 --- a/src/connector/src/source/filesystem/opendal_source/posix_fs_source.rs +++ b/src/connector/src/source/filesystem/opendal_source/posix_fs_source.rs @@ -49,11 +49,13 @@ impl OpendalEnumerator { } else { (None, None) }; + let compression_format = posix_fs_properties.compression_format; Ok(Self { op, prefix, matcher, marker: PhantomData, + compression_format, }) } } diff --git a/src/connector/src/source/filesystem/opendal_source/s3_source.rs b/src/connector/src/source/filesystem/opendal_source/s3_source.rs index fd41c44e1f7d6..2eb6b7a292506 100644 --- a/src/connector/src/source/filesystem/opendal_source/s3_source.rs +++ b/src/connector/src/source/filesystem/opendal_source/s3_source.rs @@ -71,7 +71,7 @@ impl OpendalEnumerator { } else { (None, None) }; - + let compression_format = s3_properties.compression_format; let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) .layer(RetryLayer::default()) @@ -82,6 +82,7 @@ impl OpendalEnumerator { prefix, matcher, marker: PhantomData, + compression_format, }) } } diff --git a/src/connector/src/source/filesystem/s3/enumerator.rs b/src/connector/src/source/filesystem/s3/enumerator.rs index 7491cac0df7c6..7a3e749cdc756 100644 --- a/src/connector/src/source/filesystem/s3/enumerator.rs +++ b/src/connector/src/source/filesystem/s3/enumerator.rs @@ -126,6 +126,7 @@ mod tests { } use super::*; + use crate::source::filesystem::file_common::CompressionFormat; use crate::source::filesystem::s3::S3PropertiesCommon; use crate::source::SourceEnumeratorContext; #[tokio::test] @@ -138,6 +139,7 @@ mod tests { access: None, secret: None, endpoint_url: None, + compression_format: CompressionFormat::None, }; let mut enumerator = S3SplitEnumerator::new(props.into(), SourceEnumeratorContext::dummy().into()) diff --git a/src/connector/src/source/filesystem/s3/mod.rs b/src/connector/src/source/filesystem/s3/mod.rs index 175c897d46c83..4e4816683872f 100644 --- a/src/connector/src/source/filesystem/s3/mod.rs +++ b/src/connector/src/source/filesystem/s3/mod.rs @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. pub mod enumerator; - use std::collections::HashMap; pub use enumerator::S3SplitEnumerator; + +use crate::source::filesystem::file_common::CompressionFormat; mod source; use serde::Deserialize; pub use source::S3FileReader; @@ -41,6 +42,8 @@ pub struct S3PropertiesCommon { pub secret: Option, #[serde(rename = "s3.endpoint_url")] pub endpoint_url: Option, + #[serde(rename = "compression_format", default = "Default::default")] + pub compression_format: CompressionFormat, } #[derive(Clone, Debug, Deserialize, PartialEq, with_options::WithOptions)] diff --git a/src/connector/src/source/filesystem/s3/source/reader.rs b/src/connector/src/source/filesystem/s3/source/reader.rs index 129b708a61521..9f94dedbfacb5 100644 --- a/src/connector/src/source/filesystem/s3/source/reader.rs +++ b/src/connector/src/source/filesystem/s3/source/reader.rs @@ -33,19 +33,21 @@ use tokio_util::io::ReaderStream; use crate::aws_utils::{default_conn_config, s3_client}; use crate::connector_common::AwsAuthProps; use crate::error::ConnectorResult; -use crate::parser::{ByteStreamSourceParserImpl, ParserConfig}; +use crate::parser::ParserConfig; use crate::source::base::{SplitMetaData, SplitReader}; use crate::source::filesystem::file_common::FsSplit; use crate::source::filesystem::nd_streaming; use crate::source::filesystem::nd_streaming::need_nd_streaming; use crate::source::filesystem::s3::S3Properties; -use crate::source::{BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SourceMeta}; +use crate::source::{ + into_chunk_stream, BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SourceMeta, +}; -const MAX_CHANNEL_BUFFER_SIZE: usize = 2048; const STREAM_READER_CAPACITY: usize = 4096; #[derive(Debug)] pub struct S3FileReader { + #[expect(dead_code)] split_offset: HashMap, bucket_name: String, s3_client: s3_client::Client, @@ -204,50 +206,34 @@ impl SplitReader for S3FileReader { } fn into_stream(self) -> BoxChunkSourceStream { - self.into_chunk_stream() + self.into_stream_inner() } } impl S3FileReader { #[try_stream(boxed, ok = StreamChunk, error = crate::error::ConnectorError)] - async fn into_chunk_stream(self) { + async fn into_stream_inner(self) { for split in self.splits { - let actor_id = self.source_ctx.actor_id.to_string(); - let fragment_id = self.source_ctx.fragment_id.to_string(); - let source_id = self.source_ctx.source_id.to_string(); - let source_name = self.source_ctx.source_name.to_string(); - let source_ctx = self.source_ctx.clone(); - - let split_id = split.id(); - let data_stream = Self::stream_read_object( self.s3_client.clone(), self.bucket_name.clone(), split, self.source_ctx.clone(), ); - - let parser = - ByteStreamSourceParserImpl::create(self.parser_config.clone(), source_ctx).await?; - let msg_stream = if need_nd_streaming(&self.parser_config.specific.encoding_config) { - parser.into_stream(nd_streaming::split_stream(data_stream)) + let data_stream = if need_nd_streaming(&self.parser_config.specific.encoding_config) { + nd_streaming::split_stream(data_stream) } else { - parser.into_stream(data_stream) + data_stream }; + + let msg_stream = into_chunk_stream( + data_stream, + self.parser_config.clone(), + self.source_ctx.clone(), + ); #[for_await] for msg in msg_stream { let msg = msg?; - self.source_ctx - .metrics - .partition_input_count - .with_label_values(&[ - &actor_id, - &source_id, - &split_id, - &source_name, - &fragment_id, - ]) - .inc_by(msg.cardinality() as u64); yield msg; } } @@ -264,6 +250,7 @@ mod tests { CommonParserConfig, CsvProperties, EncodingProperties, ProtocolProperties, SpecificParserConfig, }; + use crate::source::filesystem::file_common::CompressionFormat; use crate::source::filesystem::s3::S3PropertiesCommon; use crate::source::filesystem::S3SplitEnumerator; use crate::source::{ @@ -280,6 +267,7 @@ mod tests { access: None, secret: None, endpoint_url: None, + compression_format: CompressionFormat::None, } .into(); let mut enumerator = @@ -303,7 +291,6 @@ mod tests { let config = ParserConfig { common: CommonParserConfig { rw_columns: descs }, specific: SpecificParserConfig { - key_encoding_config: None, encoding_config: EncodingProperties::Csv(csv_config), protocol_config: ProtocolProperties::Plain, }, @@ -313,7 +300,7 @@ mod tests { .await .unwrap(); - let msg_stream = reader.into_chunk_stream(); + let msg_stream = reader.into_stream_inner(); #[for_await] for msg in msg_stream { println!("msg {:?}", msg); diff --git a/src/connector/src/source/google_pubsub/enumerator/client.rs b/src/connector/src/source/google_pubsub/enumerator/client.rs index 25cb28909c479..3bb37845763ea 100644 --- a/src/connector/src/source/google_pubsub/enumerator/client.rs +++ b/src/connector/src/source/google_pubsub/enumerator/client.rs @@ -15,8 +15,7 @@ use anyhow::Context; use async_trait::async_trait; use chrono::{TimeZone, Utc}; -use google_cloud_pubsub::client::{Client, ClientConfig}; -use google_cloud_pubsub::subscription::{SeekTo, SubscriptionConfig}; +use google_cloud_pubsub::subscription::SeekTo; use risingwave_common::bail; use crate::error::ConnectorResult; @@ -39,41 +38,22 @@ impl SplitEnumerator for PubsubSplitEnumerator { properties: Self::Properties, _context: SourceEnumeratorContextRef, ) -> ConnectorResult { - let subscription = properties.subscription.to_owned(); + let split_count = properties.parallelism.unwrap_or(1); + if split_count < 1 { + bail!("parallelism must be >= 1"); + }; if properties.credentials.is_none() && properties.emulator_host.is_none() { bail!("credentials must be set if not using the pubsub emulator") } - properties.initialize_env(); - - // Validate config - let config = ClientConfig::default().with_auth().await?; - let client = Client::new(config) - .await - .context("error initializing pubsub client")?; - - let sub = client.subscription(&subscription); + let sub = properties.subscription_client().await?; if !sub .exists(None) .await .context("error checking subscription validity")? { - bail!("subscription {} does not exist", &subscription) - } - - // We need the `retain_acked_messages` configuration to be true to seek back to timestamps - // as done in the [`PubsubSplitReader`] and here. - let (_, subscription_config) = sub - .config(None) - .await - .context("failed to fetch subscription config")?; - if let SubscriptionConfig { - retain_acked_messages: false, - .. - } = subscription_config - { - bail!("subscription must be configured with retain_acked_messages set to true") + bail!("subscription {} does not exist", &sub.id()) } let seek_to = match (properties.start_offset, properties.start_snapshot) { @@ -98,8 +78,8 @@ impl SplitEnumerator for PubsubSplitEnumerator { } Ok(Self { - subscription, - split_count: 1, + subscription: properties.subscription.to_owned(), + split_count, }) } @@ -109,8 +89,8 @@ impl SplitEnumerator for PubsubSplitEnumerator { .map(|i| PubsubSplit { index: i, subscription: self.subscription.to_owned(), - start_offset: None, - stop_offset: None, + __deprecated_start_offset: None, + __deprecated_stop_offset: None, }) .collect(); diff --git a/src/connector/src/source/google_pubsub/mod.rs b/src/connector/src/source/google_pubsub/mod.rs index 0a49fa6467f66..a0794bb1205df 100644 --- a/src/connector/src/source/google_pubsub/mod.rs +++ b/src/connector/src/source/google_pubsub/mod.rs @@ -14,26 +14,39 @@ use std::collections::HashMap; +use anyhow::Context; +use google_cloud_pubsub::client::{Client, ClientConfig}; +use google_cloud_pubsub::subscription::Subscription; use serde::Deserialize; pub mod enumerator; pub mod source; pub mod split; - pub use enumerator::*; +use serde_with::{serde_as, DisplayFromStr}; pub use source::*; pub use split::*; use with_options::WithOptions; +use crate::error::ConnectorResult; use crate::source::SourceProperties; pub const GOOGLE_PUBSUB_CONNECTOR: &str = "google_pubsub"; +/// # Implementation Notes +/// Pub/Sub does not rely on persisted state (`SplitImpl`) to start from a position. +/// It rely on Pub/Sub to load-balance messages between all Readers. +/// We `ack` received messages after checkpoint (see `WaitCheckpointWorker`) to achieve at-least-once delivery. +#[serde_as] #[derive(Clone, Debug, Deserialize, WithOptions)] pub struct PubsubProperties { - /// pubsub subscription to consume messages from - /// The subscription should be configured with the `retain-on-ack` property to enable - /// message recovery within risingwave. + /// Pub/Sub subscription to consume messages from. + /// + /// Note that we rely on Pub/Sub to load-balance messages between all Readers pulling from + /// the same subscription. So one `subscription` (i.e., one `Source`) can only used for one MV + /// (shared between the actors of its fragment). + /// Otherwise, different MVs on the same Source will both receive part of the messages. + /// TODO: check and enforce this on Meta. #[serde(rename = "pubsub.subscription")] pub subscription: String, @@ -62,12 +75,18 @@ pub struct PubsubProperties { /// in pub/sub because they guarantee retention of: /// - All unacknowledged messages at the time of their creation. /// - All messages created after their creation. - /// Besides retention guarantees, timestamps are also more precise than timestamp-based seeks. + /// Besides retention guarantees, snapshots are also more precise than timestamp-based seeks. /// See [Seeking to a snapshot](https://cloud.google.com/pubsub/docs/replay-overview#seeking_to_a_timestamp) for /// more details. #[serde(rename = "pubsub.start_snapshot")] pub start_snapshot: Option, + /// `parallelism` is the number of parallel consumers to run for the subscription. + /// TODO: use system parallelism if not set + #[serde_as(as = "Option")] + #[serde(rename = "pubsub.parallelism")] + pub parallelism: Option, + #[serde(flatten)] pub unknown_fields: HashMap, } @@ -97,6 +116,18 @@ impl PubsubProperties { std::env::set_var("GOOGLE_APPLICATION_CREDENTIALS_JSON", credentials); } } + + pub(crate) async fn subscription_client(&self) -> ConnectorResult { + self.initialize_env(); + + // Validate config + let config = ClientConfig::default().with_auth().await?; + let client = Client::new(config) + .await + .context("error initializing pubsub client")?; + + Ok(client.subscription(&self.subscription)) + } } #[cfg(test)] @@ -123,7 +154,7 @@ mod tests { start_offset: None, start_snapshot: None, subscription: String::from("test-subscription"), - + parallelism: None, unknown_fields: Default::default(), }; diff --git a/src/connector/src/source/google_pubsub/source/message.rs b/src/connector/src/source/google_pubsub/source/message.rs index cf14757b3668b..10194453bebbc 100644 --- a/src/connector/src/source/google_pubsub/source/message.rs +++ b/src/connector/src/source/google_pubsub/source/message.rs @@ -31,6 +31,7 @@ impl From for SourceMessage { fn from(tagged_message: TaggedReceivedMessage) -> Self { let TaggedReceivedMessage(split_id, message) = tagged_message; + let ack_id = message.ack_id().to_string(); let timestamp = message .message .publish_time @@ -50,8 +51,9 @@ impl From for SourceMessage { _ => Some(payload), } }, - offset: timestamp.timestamp_nanos_opt().unwrap().to_string(), + offset: ack_id, split_id, + // What's the usage of this? meta: SourceMeta::GooglePubsub(GooglePubsubMeta { timestamp: Some(timestamp.timestamp_millis()), }), diff --git a/src/connector/src/source/google_pubsub/source/reader.rs b/src/connector/src/source/google_pubsub/source/reader.rs index 0887cb06594f9..abc3ecd7743e3 100644 --- a/src/connector/src/source/google_pubsub/source/reader.rs +++ b/src/connector/src/source/google_pubsub/source/reader.rs @@ -12,12 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use anyhow::{anyhow, Context}; use async_trait::async_trait; -use chrono::{NaiveDateTime, TimeZone, Utc}; use futures_async_stream::try_stream; -use google_cloud_pubsub::client::{Client, ClientConfig}; -use google_cloud_pubsub::subscription::{SeekTo, Subscription}; +use google_cloud_pubsub::subscription::Subscription; use risingwave_common::{bail, ensure}; use tonic::Code; @@ -26,22 +23,21 @@ use crate::error::{ConnectorError, ConnectorResult as Result}; use crate::parser::ParserConfig; use crate::source::google_pubsub::{PubsubProperties, PubsubSplit}; use crate::source::{ - into_chunk_stream, BoxChunkSourceStream, Column, CommonSplitReader, SourceContextRef, - SourceMessage, SplitId, SplitMetaData, SplitReader, + into_chunk_stream, BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SplitId, + SplitMetaData, SplitReader, }; const PUBSUB_MAX_FETCH_MESSAGES: usize = 1024; pub struct PubsubSplitReader { subscription: Subscription, - stop_offset: Option, split_id: SplitId, parser_config: ParserConfig, source_ctx: SourceContextRef, } -impl CommonSplitReader for PubsubSplitReader { +impl PubsubSplitReader { #[try_stream(ok = Vec, error = ConnectorError)] async fn into_data_stream(self) { loop { @@ -67,41 +63,16 @@ impl CommonSplitReader for PubsubSplitReader { continue; } - let latest_offset: NaiveDateTime = raw_chunk - .last() - .map(|m| m.message.publish_time.clone().unwrap_or_default()) - .map(|t| { - let mut t = t; - t.normalize(); - NaiveDateTime::from_timestamp_opt(t.seconds, t.nanos as u32).unwrap_or_default() - }) - .unwrap_or_default(); - let mut chunk: Vec = Vec::with_capacity(raw_chunk.len()); - let mut ack_ids: Vec = Vec::with_capacity(raw_chunk.len()); for message in raw_chunk { - ack_ids.push(message.ack_id().into()); chunk.push(SourceMessage::from(TaggedReceivedMessage( self.split_id.clone(), message, ))); } - self.subscription - .ack(ack_ids) - .await - .map_err(|e| anyhow!(e)) - .context("failed to ack pubsub messages")?; - yield chunk; - - // Stop if we've approached the stop_offset - if let Some(stop_offset) = self.stop_offset - && latest_offset >= stop_offset - { - return Ok(()); - } } } } @@ -124,42 +95,11 @@ impl SplitReader for PubsubSplitReader { ); let split = splits.into_iter().next().unwrap(); - // Set environment variables consumed by `google_cloud_pubsub` - properties.initialize_env(); - - let config = ClientConfig::default().with_auth().await?; - let client = Client::new(config).await.map_err(|e| anyhow!(e))?; - let subscription = client.subscription(&properties.subscription); - - if let Some(ref offset) = split.start_offset { - let timestamp = offset - .as_str() - .parse::() - .map(|nanos| Utc.timestamp_nanos(nanos)) - .context("error parsing offset")?; - - subscription - .seek(SeekTo::Timestamp(timestamp.into()), None) - .await - .context("error seeking to pubsub offset")?; - } - - let stop_offset = if let Some(ref offset) = split.stop_offset { - Some( - offset - .as_str() - .parse::() - .map_err(|e| anyhow!(e)) - .map(|nanos| NaiveDateTime::from_timestamp_opt(nanos, 0).unwrap_or_default())?, - ) - } else { - None - }; + let subscription = properties.subscription_client().await?; Ok(Self { subscription, split_id: split.id(), - stop_offset, parser_config, source_ctx, }) @@ -168,6 +108,6 @@ impl SplitReader for PubsubSplitReader { fn into_stream(self) -> BoxChunkSourceStream { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } diff --git a/src/connector/src/source/google_pubsub/split.rs b/src/connector/src/source/google_pubsub/split.rs index 14c40150488a1..85d32f479bc2e 100644 --- a/src/connector/src/source/google_pubsub/split.rs +++ b/src/connector/src/source/google_pubsub/split.rs @@ -20,19 +20,18 @@ use crate::source::{SplitId, SplitMetaData}; #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Hash)] pub struct PubsubSplit { + // XXX: `index` and `subscription` seems also not useful. It's only for `SplitMetaData::id`. + // Is the split id useful? pub(crate) index: u32, pub(crate) subscription: String, - /// `start_offset` is a numeric timestamp. - /// When not `None`, the `PubsubReader` seeks to the timestamp described by the `start_offset`. - /// These offsets are taken from the `offset` property of the `SourceMessage` yielded by the - /// pubsub reader. - pub(crate) start_offset: Option, + #[serde(rename = "start_offset")] + #[serde(skip_serializing)] + pub(crate) __deprecated_start_offset: Option, - /// `stop_offset` is a numeric timestamp. - /// When not `None`, the `PubsubReader` stops reading messages when the `offset` property of - /// the `SourceMessage` is greater than or equal to the `stop_offset`. - pub(crate) stop_offset: Option, + #[serde(rename = "stop_offset")] + #[serde(skip_serializing)] + pub(crate) __deprecated_stop_offset: Option, } impl SplitMetaData for PubsubSplit { @@ -48,8 +47,11 @@ impl SplitMetaData for PubsubSplit { format!("{}-{}", self.subscription, self.index).into() } - fn update_offset(&mut self, last_seen_offset: String) -> ConnectorResult<()> { - self.start_offset = Some(last_seen_offset); + /// No-op. Actually `PubsubSplit` doesn't maintain any state. It's fully managed by Pubsub. + /// One subscription is like one Kafka consumer group. + fn update_offset(&mut self, _last_seen_offset: String) -> ConnectorResult<()> { + // forcefully set previously persisted start_offset to None + self.__deprecated_start_offset = None; Ok(()) } } diff --git a/src/connector/src/source/iceberg/mod.rs b/src/connector/src/source/iceberg/mod.rs index e7e4971dd42c5..92880341b588e 100644 --- a/src/connector/src/source/iceberg/mod.rs +++ b/src/connector/src/source/iceberg/mod.rs @@ -16,7 +16,8 @@ use std::collections::HashMap; use anyhow::anyhow; use async_trait::async_trait; -use icelake::types::DataContentType; +use futures::StreamExt; +use iceberg::spec::{DataContentType, ManifestList}; use itertools::Itertools; use risingwave_common::bail; use risingwave_common::types::JsonbVal; @@ -38,11 +39,11 @@ pub struct IcebergProperties { pub catalog_type: Option, #[serde(rename = "s3.region")] pub region: Option, - #[serde(rename = "s3.endpoint", default)] - pub endpoint: String, - #[serde(rename = "s3.access.key", default)] + #[serde(rename = "s3.endpoint")] + pub endpoint: Option, + #[serde(rename = "s3.access.key")] pub s3_access: String, - #[serde(rename = "s3.secret.key", default)] + #[serde(rename = "s3.secret.key")] pub s3_secret: String, #[serde(rename = "warehouse.path")] pub warehouse_path: String, @@ -82,7 +83,7 @@ impl IcebergProperties { catalog_type: self.catalog_type.clone(), uri: self.catalog_uri.clone(), path: self.warehouse_path.clone(), - endpoint: Some(self.endpoint.clone()), + endpoint: self.endpoint.clone(), access_key: self.s3_access.clone(), secret_key: self.s3_secret.clone(), region: self.region.clone(), @@ -171,58 +172,64 @@ impl IcebergSplitEnumerator { if batch_parallelism == 0 { bail!("Batch parallelism is 0. Cannot split the iceberg files."); } - let table = self.config.load_table().await?; + let table = self.config.load_table_v2().await?; let snapshot_id = match time_traval_info { Some(IcebergTimeTravelInfo::Version(version)) => { - let Some(snapshot) = table.current_table_metadata().snapshot(version) else { + let Some(snapshot) = table.metadata().snapshot_by_id(version) else { bail!("Cannot find the snapshot id in the iceberg table."); }; - snapshot.snapshot_id + snapshot.snapshot_id() } Some(IcebergTimeTravelInfo::TimestampMs(timestamp)) => { - match &table.current_table_metadata().snapshots { - Some(snapshots) => { - let snapshot = snapshots - .iter() - .filter(|snapshot| snapshot.timestamp_ms <= timestamp) - .max_by_key(|snapshot| snapshot.timestamp_ms); - match snapshot { - Some(snapshot) => snapshot.snapshot_id, - None => { - // convert unix time to human readable time - let time = chrono::NaiveDateTime::from_timestamp_millis(timestamp); - if time.is_some() { - bail!("Cannot find a snapshot older than {}", time.unwrap()); - } else { - bail!("Cannot find a snapshot"); - } - } - } - } + let snapshot = table + .metadata() + .snapshots() + .filter(|snapshot| snapshot.timestamp().timestamp_millis() <= timestamp) + .max_by_key(|snapshot| snapshot.timestamp().timestamp_millis()); + match snapshot { + Some(snapshot) => snapshot.snapshot_id(), None => { - bail!("Cannot find the snapshots in the iceberg table."); + // convert unix time to human readable time + let time = chrono::DateTime::from_timestamp_millis(timestamp); + if time.is_some() { + bail!("Cannot find a snapshot older than {}", time.unwrap()); + } else { + bail!("Cannot find a snapshot"); + } } } } - None => match table.current_table_metadata().current_snapshot_id { - Some(snapshot_id) => snapshot_id, + None => match table.metadata().current_snapshot() { + Some(snapshot) => snapshot.snapshot_id(), None => bail!("Cannot find the current snapshot id in the iceberg table."), }, }; let mut files = vec![]; - for file in table - .data_files_of_snapshot( - table - .current_table_metadata() - .snapshot(snapshot_id) - .expect("snapshot must exists"), - ) - .await? - { - if file.content != DataContentType::Data { - bail!("Reading iceberg table with delete files is unsupported. Please try to compact the table first."); + + let snapshot = table + .metadata() + .snapshot_by_id(snapshot_id) + .expect("snapshot must exist"); + + let manifest_list: ManifestList = snapshot + .load_manifest_list(table.file_io(), table.metadata()) + .await + .map_err(|e| anyhow!(e))?; + for entry in manifest_list.entries() { + let manifest = entry + .load_manifest(table.file_io()) + .await + .map_err(|e| anyhow!(e))?; + let mut manifest_entries_stream = + futures::stream::iter(manifest.entries().iter().filter(|e| e.is_alive())); + + while let Some(manifest_entry) = manifest_entries_stream.next().await { + let file = manifest_entry.data_file(); + if file.content_type() != DataContentType::Data { + bail!("Reading iceberg table with delete files is unsupported. Please try to compact the table first."); + } + files.push(file.file_path().to_string()); } - files.push(file.file_path); } let split_num = batch_parallelism; // evenly split the files into splits based on the parallelism. diff --git a/src/connector/src/source/kafka/client_context.rs b/src/connector/src/source/kafka/client_context.rs new file mode 100644 index 0000000000000..d788411ff15f5 --- /dev/null +++ b/src/connector/src/source/kafka/client_context.rs @@ -0,0 +1,227 @@ +// Copyright 2024 RisingWave Labs +// +// 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::BTreeMap; +use std::sync::Arc; +use std::thread; + +use anyhow::anyhow; +use aws_config::Region; +use aws_sdk_s3::config::SharedCredentialsProvider; +use rdkafka::client::{BrokerAddr, OAuthToken}; +use rdkafka::consumer::ConsumerContext; +use rdkafka::message::DeliveryResult; +use rdkafka::producer::ProducerContext; +use rdkafka::{ClientContext, Statistics}; + +use super::private_link::{BrokerAddrRewriter, PrivateLinkContextRole}; +use super::stats::RdKafkaStats; +use crate::connector_common::AwsAuthProps; +use crate::error::ConnectorResult; + +struct IamAuthEnv { + credentials_provider: SharedCredentialsProvider, + region: Region, + // XXX(runji): madsim does not support `Handle` for now + #[cfg(not(madsim))] + rt: tokio::runtime::Handle, +} + +pub struct KafkaContextCommon { + // For VPC PrivateLink support + addr_rewriter: BrokerAddrRewriter, + + // identifier is required when reporting metrics as a label, usually it is compose by connector + // format (source or sink) and corresponding id (source_id or sink_id) + // identifier and metrics should be set at the same time + identifier: Option, + metrics: Option>, + + /// Credential and region for AWS MSK + auth: Option, +} + +impl KafkaContextCommon { + pub async fn new( + broker_rewrite_map: Option>, + identifier: Option, + metrics: Option>, + auth: AwsAuthProps, + is_aws_msk_iam: bool, + ) -> ConnectorResult { + let addr_rewriter = + BrokerAddrRewriter::new(PrivateLinkContextRole::Consumer, broker_rewrite_map)?; + let auth = if is_aws_msk_iam { + let config = auth.build_config().await?; + let credentials_provider = config + .credentials_provider() + .ok_or_else(|| anyhow!("missing aws credentials_provider"))?; + let region = config + .region() + .ok_or_else(|| anyhow!("missing aws region"))? + .clone(); + Some(IamAuthEnv { + credentials_provider, + region, + #[cfg(not(madsim))] + rt: tokio::runtime::Handle::current(), + }) + } else { + None + }; + Ok(Self { + addr_rewriter, + identifier, + metrics, + auth, + }) + } +} + +impl KafkaContextCommon { + fn stats(&self, statistics: Statistics) { + if let Some(metrics) = &self.metrics + && let Some(id) = &self.identifier + { + metrics.report(id.as_str(), &statistics); + } + } + + fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + self.addr_rewriter.rewrite_broker_addr(addr) + } + + // XXX(runji): oauth is ignored in simulation + #[cfg_or_panic::cfg_or_panic(not(madsim))] + fn generate_oauth_token( + &self, + _oauthbearer_config: Option<&str>, + ) -> Result> { + use aws_msk_iam_sasl_signer::generate_auth_token_from_credentials_provider; + use tokio::time::{timeout, Duration}; + + if let Some(IamAuthEnv { + credentials_provider, + region, + rt, + }) = &self.auth + { + let region = region.clone(); + let credentials_provider = credentials_provider.clone(); + let rt = rt.clone(); + let (token, expiration_time_ms) = { + let handle = thread::spawn(move || { + rt.block_on(async { + timeout( + Duration::from_secs(10), + generate_auth_token_from_credentials_provider( + region, + credentials_provider, + ), + ) + .await + }) + }); + handle.join().unwrap()?? + }; + Ok(OAuthToken { + token, + principal_name: "".to_string(), + lifetime_ms: expiration_time_ms, + }) + } else { + Err("must provide AWS IAM credential".into()) + } + } + + fn enable_refresh_oauth_token(&self) -> bool { + self.auth.is_some() + } +} + +pub type BoxConsumerContext = Box; + +/// Kafka consumer context used for private link, IAM auth, and metrics +pub struct RwConsumerContext { + common: KafkaContextCommon, +} + +impl RwConsumerContext { + pub fn new(common: KafkaContextCommon) -> Self { + Self { common } + } +} + +impl ClientContext for RwConsumerContext { + /// this func serves as a callback when `poll` is completed. + fn stats(&self, statistics: Statistics) { + self.common.stats(statistics); + } + + fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + self.common.rewrite_broker_addr(addr) + } + + fn generate_oauth_token( + &self, + oauthbearer_config: Option<&str>, + ) -> Result> { + self.common.generate_oauth_token(oauthbearer_config) + } + + fn enable_refresh_oauth_token(&self) -> bool { + self.common.enable_refresh_oauth_token() + } +} + +// required by the trait bound of BaseConsumer +impl ConsumerContext for RwConsumerContext {} + +/// Kafka producer context used for private link, IAM auth, and metrics +pub struct RwProducerContext { + common: KafkaContextCommon, +} + +impl RwProducerContext { + pub fn new(common: KafkaContextCommon) -> Self { + Self { common } + } +} + +impl ClientContext for RwProducerContext { + fn stats(&self, statistics: Statistics) { + self.common.stats(statistics); + } + + fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + self.common.rewrite_broker_addr(addr) + } + + fn generate_oauth_token( + &self, + oauthbearer_config: Option<&str>, + ) -> Result> { + self.common.generate_oauth_token(oauthbearer_config) + } + + fn enable_refresh_oauth_token(&self) -> bool { + self.common.enable_refresh_oauth_token() + } +} + +impl ProducerContext for RwProducerContext { + type DeliveryOpaque = (); + + fn delivery(&self, _: &DeliveryResult<'_>, _: Self::DeliveryOpaque) {} +} diff --git a/src/connector/src/source/kafka/enumerator/client.rs b/src/connector/src/source/kafka/enumerator/client.rs index 697fe421a5fb2..d4dddad67bfdf 100644 --- a/src/connector/src/source/kafka/enumerator/client.rs +++ b/src/connector/src/source/kafka/enumerator/client.rs @@ -25,7 +25,9 @@ use risingwave_common::bail; use crate::error::ConnectorResult; use crate::source::base::SplitEnumerator; use crate::source::kafka::split::KafkaSplit; -use crate::source::kafka::{KafkaProperties, PrivateLinkConsumerContext, KAFKA_ISOLATION_LEVEL}; +use crate::source::kafka::{ + KafkaContextCommon, KafkaProperties, RwConsumerContext, KAFKA_ISOLATION_LEVEL, +}; use crate::source::SourceEnumeratorContextRef; #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -40,7 +42,7 @@ pub struct KafkaSplitEnumerator { context: SourceEnumeratorContextRef, broker_address: String, topic: String, - client: BaseConsumer, + client: BaseConsumer, start_offset: KafkaEnumeratorOffset, // maybe used in the future for batch processing @@ -90,10 +92,30 @@ impl SplitEnumerator for KafkaSplitEnumerator { } // don't need kafka metrics from enumerator - let client_ctx = PrivateLinkConsumerContext::new(broker_rewrite_map, None, None)?; - let client: BaseConsumer = + let ctx_common = KafkaContextCommon::new( + broker_rewrite_map, + None, + None, + properties.aws_auth_props, + common_props.is_aws_msk_iam(), + ) + .await?; + let client_ctx = RwConsumerContext::new(ctx_common); + let client: BaseConsumer = config.create_with_context(client_ctx).await?; + // Note that before any SASL/OAUTHBEARER broker connection can succeed the application must call + // rd_kafka_oauthbearer_set_token() once – either directly or, more typically, by invoking either + // rd_kafka_poll(), rd_kafka_consumer_poll(), rd_kafka_queue_poll(), etc, in order to cause retrieval + // of an initial token to occur. + // https://docs.confluent.io/platform/current/clients/librdkafka/html/rdkafka_8h.html#a988395722598f63396d7a1bedb22adaf + if common_props.is_aws_msk_iam() { + #[cfg(not(madsim))] + client.poll(Duration::from_secs(10)); // note: this is a blocking call + #[cfg(madsim)] + client.poll(Duration::from_secs(10)).await; + } + Ok(Self { context, broker_address, @@ -129,6 +151,7 @@ impl SplitEnumerator for KafkaSplitEnumerator { partition, start_offset: start_offsets.remove(&partition).unwrap(), stop_offset: stop_offsets.remove(&partition).unwrap(), + hack_seek_to_latest: false, }) .collect(); @@ -232,6 +255,7 @@ impl KafkaSplitEnumerator { partition: *partition, start_offset: Some(start_offset), stop_offset: Some(stop_offset), + hack_seek_to_latest:false } }) .collect::>()) diff --git a/src/connector/src/source/kafka/mod.rs b/src/connector/src/source/kafka/mod.rs index 91d4ccce5ca88..2360f9fb8a337 100644 --- a/src/connector/src/source/kafka/mod.rs +++ b/src/connector/src/source/kafka/mod.rs @@ -17,16 +17,17 @@ use std::collections::HashMap; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; -use crate::connector_common::KafkaPrivateLinkCommon; +use crate::connector_common::{AwsAuthProps, KafkaPrivateLinkCommon}; +mod client_context; pub mod enumerator; pub mod private_link; pub mod source; pub mod split; pub mod stats; +pub use client_context::*; pub use enumerator::*; -pub use private_link::*; pub use source::*; pub use split::*; use with_options::WithOptions; @@ -137,6 +138,9 @@ pub struct KafkaProperties { #[serde(flatten)] pub privatelink_common: KafkaPrivateLinkCommon, + #[serde(flatten)] + pub aws_auth_props: AwsAuthProps, + #[serde(flatten)] pub unknown_fields: HashMap, } @@ -159,8 +163,6 @@ impl KafkaProperties { pub fn set_client(&self, c: &mut rdkafka::ClientConfig) { self.rdkafka_properties_common.set_client(c); self.rdkafka_properties_consumer.set_client(c); - - tracing::info!("kafka client starts with: {:?}", c); } } @@ -191,15 +193,15 @@ impl RdKafkaPropertiesConsumer { #[cfg(test)] mod test { - use std::collections::HashMap; + use std::collections::BTreeMap; - use maplit::hashmap; + use maplit::btreemap; use super::*; #[test] fn test_parse_config_consumer_common() { - let config: HashMap = hashmap! { + let config: BTreeMap = btreemap! { // common "properties.bootstrap.server".to_string() => "127.0.0.1:9092".to_string(), "topic".to_string() => "test".to_string(), @@ -255,7 +257,7 @@ mod test { props.rdkafka_properties_consumer.fetch_queue_backoff_ms, Some(114514) ); - let hashmap: HashMap = hashmap! { + let hashmap: BTreeMap = btreemap! { "broker1".to_string() => "10.0.0.1:8001".to_string() }; assert_eq!(props.privatelink_common.broker_rewrite_map, Some(hashmap)); diff --git a/src/connector/src/source/kafka/private_link.rs b/src/connector/src/source/kafka/private_link.rs index 6187078ae24fb..6aeebde87b516 100644 --- a/src/connector/src/source/kafka/private_link.rs +++ b/src/connector/src/source/kafka/private_link.rs @@ -14,14 +14,10 @@ use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; -use std::sync::Arc; use anyhow::{anyhow, Context}; use itertools::Itertools; use rdkafka::client::BrokerAddr; -use rdkafka::consumer::ConsumerContext; -use rdkafka::producer::{DeliveryResult, ProducerContext}; -use rdkafka::{ClientContext, Statistics}; use risingwave_common::bail; use risingwave_common::util::addr::HostAddr; use risingwave_common::util::iter_util::ZipEqFast; @@ -31,15 +27,15 @@ use crate::connector_common::{ AwsPrivateLinkItem, PRIVATE_LINK_BROKER_REWRITE_MAP_KEY, PRIVATE_LINK_TARGETS_KEY, }; use crate::error::ConnectorResult; -use crate::source::kafka::stats::RdKafkaStats; use crate::source::kafka::{KAFKA_PROPS_BROKER_KEY, KAFKA_PROPS_BROKER_KEY_ALIAS}; pub const PRIVATELINK_ENDPOINT_KEY: &str = "privatelink.endpoint"; pub const CONNECTION_NAME_KEY: &str = "connection.name"; #[derive(Debug)] -enum PrivateLinkContextRole { +pub(super) enum PrivateLinkContextRole { Consumer, + #[expect(dead_code)] Producer, } @@ -52,13 +48,14 @@ impl std::fmt::Display for PrivateLinkContextRole { } } -struct BrokerAddrRewriter { +pub(super) struct BrokerAddrRewriter { + #[expect(dead_code)] role: PrivateLinkContextRole, rewrite_map: BTreeMap, } impl BrokerAddrRewriter { - fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + pub(super) fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { let rewrote_addr = match self.rewrite_map.get(&addr) { None => addr, Some(new_addr) => new_addr.clone(), @@ -68,7 +65,7 @@ impl BrokerAddrRewriter { pub fn new( role: PrivateLinkContextRole, - broker_rewrite_map: Option>, + broker_rewrite_map: Option>, ) -> ConnectorResult { let rewrite_map: ConnectorResult> = broker_rewrite_map .map_or(Ok(BTreeMap::new()), |addr_map| { @@ -95,94 +92,6 @@ impl BrokerAddrRewriter { } } -pub struct PrivateLinkConsumerContext { - inner: BrokerAddrRewriter, - - // identifier is required when reporting metrics as a label, usually it is compose by connector - // format (source or sink) and corresponding id (source_id or sink_id) - // identifier and metrics should be set at the same time - identifier: Option, - metrics: Option>, -} - -impl PrivateLinkConsumerContext { - pub fn new( - broker_rewrite_map: Option>, - identifier: Option, - metrics: Option>, - ) -> ConnectorResult { - let inner = BrokerAddrRewriter::new(PrivateLinkContextRole::Consumer, broker_rewrite_map)?; - Ok(Self { - inner, - identifier, - metrics, - }) - } -} - -impl ClientContext for PrivateLinkConsumerContext { - /// this func serves as a callback when `poll` is completed. - fn stats(&self, statistics: Statistics) { - if let Some(metrics) = &self.metrics - && let Some(id) = &self.identifier - { - metrics.report(id.as_str(), &statistics); - } - } - - fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { - self.inner.rewrite_broker_addr(addr) - } -} - -// required by the trait bound of BaseConsumer -impl ConsumerContext for PrivateLinkConsumerContext {} - -pub struct PrivateLinkProducerContext { - inner: BrokerAddrRewriter, - - // identifier is required when reporting metrics as a label, usually it is compose by connector - // format (source or sink) and corresponding id (source_id or sink_id) - // identifier and metrics should be set at the same time - identifier: Option, - metrics: Option>, -} - -impl PrivateLinkProducerContext { - pub fn new( - broker_rewrite_map: Option>, - identifier: Option, - metrics: Option>, - ) -> ConnectorResult { - let inner = BrokerAddrRewriter::new(PrivateLinkContextRole::Producer, broker_rewrite_map)?; - Ok(Self { - inner, - identifier, - metrics, - }) - } -} - -impl ClientContext for PrivateLinkProducerContext { - fn stats(&self, statistics: Statistics) { - if let Some(metrics) = &self.metrics - && let Some(id) = &self.identifier - { - metrics.report(id.as_str(), &statistics); - } - } - - fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { - self.inner.rewrite_broker_addr(addr) - } -} - -impl ProducerContext for PrivateLinkProducerContext { - type DeliveryOpaque = (); - - fn delivery(&self, _: &DeliveryResult<'_>, _: Self::DeliveryOpaque) {} -} - #[inline(always)] fn kafka_props_broker_key(with_properties: &BTreeMap) -> &str { if with_properties.contains_key(KAFKA_PROPS_BROKER_KEY) { diff --git a/src/connector/src/source/kafka/source/message.rs b/src/connector/src/source/kafka/source/message.rs index 0ef55dc79132d..247166a156763 100644 --- a/src/connector/src/source/kafka/source/message.rs +++ b/src/connector/src/source/kafka/source/message.rs @@ -12,10 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::borrow::Cow; + use itertools::Itertools; use rdkafka::message::{BorrowedMessage, Headers, OwnedHeaders}; use rdkafka::Message; -use risingwave_common::types::{Datum, ListValue, Scalar, ScalarImpl, StructValue}; +use risingwave_common::types::{ + Datum, DatumCow, DatumRef, ListValue, ScalarImpl, ScalarRefImpl, StructValue, +}; use risingwave_pb::data::data_type::TypeName as PbTypeName; use risingwave_pb::data::DataType as PbDataType; @@ -31,39 +35,42 @@ pub struct KafkaMeta { } impl KafkaMeta { - pub fn extract_timestamp(&self) -> Option { - self.timestamp - .map(|ts| { - risingwave_common::cast::i64_to_timestamptz(ts) - .unwrap() - .to_scalar_value() - }) - .into() + pub fn extract_timestamp(&self) -> Option> { + self.timestamp.map(|ts| { + Some(ScalarRefImpl::Timestamptz( + risingwave_common::cast::i64_to_timestamptz(ts).unwrap(), + )) + }) } - pub fn extract_header_inner( - &self, + pub fn extract_header_inner<'a>( + &'a self, inner_field: &str, data_type: Option<&PbDataType>, - ) -> Option { - let target_value = self - .headers - .as_ref() - .iter() - .find_map(|headers| { - headers - .iter() - .find(|header| header.key == inner_field) - .map(|header| header.value) - }) - .unwrap_or(None); // if not found the specified column, return None - if let Some(data_type) = data_type + ) -> Option> { + let target_value = self.headers.as_ref().iter().find_map(|headers| { + headers + .iter() + .find(|header| header.key == inner_field) + .map(|header| header.value) + })?; // if not found the specified column, return None + + let Some(target_value) = target_value else { + return Some(Datum::None.into()); + }; + + let datum = if let Some(data_type) = data_type && data_type.type_name == PbTypeName::Varchar as i32 { - Some(target_value.map(|byte| ScalarImpl::Utf8(String::from_utf8_lossy(byte).into()))) + match String::from_utf8_lossy(target_value) { + Cow::Borrowed(str) => Some(ScalarRefImpl::Utf8(str)).into(), + Cow::Owned(string) => Some(ScalarImpl::Utf8(string.into())).into(), + } } else { - Some(target_value.map(|byte| ScalarImpl::Bytea(byte.into()))) - } + Some(ScalarRefImpl::Bytea(target_value)).into() + }; + + Some(datum) } pub fn extract_headers(&self) -> Option { diff --git a/src/connector/src/source/kafka/source/reader.rs b/src/connector/src/source/kafka/source/reader.rs index 73c24dd5f810d..c54b0ac27655a 100644 --- a/src/connector/src/source/kafka/source/reader.rs +++ b/src/connector/src/source/kafka/source/reader.rs @@ -31,15 +31,15 @@ use crate::error::ConnectorResult as Result; use crate::parser::ParserConfig; use crate::source::base::SourceMessage; use crate::source::kafka::{ - KafkaProperties, KafkaSplit, PrivateLinkConsumerContext, KAFKA_ISOLATION_LEVEL, + KafkaContextCommon, KafkaProperties, KafkaSplit, RwConsumerContext, KAFKA_ISOLATION_LEVEL, }; use crate::source::{ - into_chunk_stream, BoxChunkSourceStream, Column, CommonSplitReader, SourceContextRef, SplitId, - SplitMetaData, SplitReader, + into_chunk_stream, BoxChunkSourceStream, Column, SourceContextRef, SplitId, SplitMetaData, + SplitReader, }; pub struct KafkaSplitReader { - consumer: StreamConsumer, + consumer: StreamConsumer, offsets: HashMap, Option)>, bytes_per_second: usize, max_num_messages: usize, @@ -78,7 +78,7 @@ impl SplitReader for KafkaSplitReader { format!("rw-consumer-{}", source_ctx.fragment_id), ); - let client_ctx = PrivateLinkConsumerContext::new( + let ctx_common = KafkaContextCommon::new( broker_rewrite_map, Some(format!( "fragment-{}-source-{}-actor-{}", @@ -87,8 +87,13 @@ impl SplitReader for KafkaSplitReader { // thread consumer will keep polling in the background, we don't need to call `poll` // explicitly Some(source_ctx.metrics.rdkafka_native_metric.clone()), - )?; - let consumer: StreamConsumer = config + properties.aws_auth_props, + properties.common.is_aws_msk_iam(), + ) + .await?; + + let client_ctx = RwConsumerContext::new(ctx_common); + let consumer: StreamConsumer = config .set_log_level(RDKafkaLogLevel::Info) .create_with_context(client_ctx) .await @@ -101,7 +106,9 @@ impl SplitReader for KafkaSplitReader { for split in splits { offsets.insert(split.id(), (split.start_offset, split.stop_offset)); - if let Some(offset) = split.start_offset { + if split.hack_seek_to_latest { + tpl.add_partition_offset(split.topic.as_str(), split.partition, Offset::End)?; + } else if let Some(offset) = split.start_offset { tpl.add_partition_offset( split.topic.as_str(), split.partition, @@ -142,7 +149,7 @@ impl SplitReader for KafkaSplitReader { fn into_stream(self) -> BoxChunkSourceStream { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } @@ -161,7 +168,7 @@ impl KafkaSplitReader { } } -impl CommonSplitReader for KafkaSplitReader { +impl KafkaSplitReader { #[try_stream(ok = Vec, error = crate::error::ConnectorError)] async fn into_data_stream(self) { if self.offsets.values().all(|(start_offset, stop_offset)| { diff --git a/src/connector/src/source/kafka/split.rs b/src/connector/src/source/kafka/split.rs index a649a729c0397..7ad0d52619b4a 100644 --- a/src/connector/src/source/kafka/split.rs +++ b/src/connector/src/source/kafka/split.rs @@ -24,6 +24,12 @@ pub struct KafkaSplit { pub(crate) partition: i32, pub(crate) start_offset: Option, pub(crate) stop_offset: Option, + #[serde(skip)] + /// Used by shared source to hackily seek to the latest offset without fetching start offset first. + /// XXX: But why do we fetch low watermark for latest start offset..? + /// + /// When this is `true`, `start_offset` will be ignored. + pub(crate) hack_seek_to_latest: bool, } impl SplitMetaData for KafkaSplit { @@ -58,10 +64,16 @@ impl KafkaSplit { partition, start_offset, stop_offset, + hack_seek_to_latest: false, } } pub fn get_topic_and_partition(&self) -> (String, i32) { (self.topic.clone(), self.partition) } + + /// This should only be used for a fresh split, not persisted in state table yet. + pub fn seek_to_latest_offset(&mut self) { + self.hack_seek_to_latest = true; + } } diff --git a/src/connector/src/source/kafka/stats.rs b/src/connector/src/source/kafka/stats.rs index 2b94b4c90a853..679f5c24bd2a1 100644 --- a/src/connector/src/source/kafka/stats.rs +++ b/src/connector/src/source/kafka/stats.rs @@ -13,30 +13,10 @@ // limitations under the License. use prometheus::core::{AtomicU64, GenericGaugeVec}; -use prometheus::{opts, register_int_gauge_vec_with_registry, IntGaugeVec, Registry}; +use prometheus::{register_int_gauge_vec_with_registry, IntGaugeVec, Registry}; use rdkafka::statistics::{Broker, ConsumerGroup, Partition, Topic, Window}; use rdkafka::Statistics; - -type UintGaugeVec = GenericGaugeVec; - -macro_rules! register_gauge_vec { - ($TYPE:ident, $OPTS:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ - let gauge_vec = $TYPE::new($OPTS, $LABELS_NAMES).unwrap(); - $REGISTRY - .register(Box::new(gauge_vec.clone())) - .map(|_| gauge_vec) - }}; -} - -macro_rules! register_uint_gauge_vec_with_registry { - ($OPTS:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ - register_gauge_vec!(UintGaugeVec, $OPTS, $LABELS_NAMES, $REGISTRY) - }}; - - ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ - register_uint_gauge_vec_with_registry!(opts!($NAME, $HELP), $LABELS_NAMES, $REGISTRY) - }}; -} +use risingwave_common::metrics::register_uint_gauge_vec_with_registry; #[derive(Debug, Clone)] pub struct RdKafkaStats { diff --git a/src/connector/src/source/kinesis/enumerator/client.rs b/src/connector/src/source/kinesis/enumerator/client.rs index b8966f99ae1ac..840def08f6855 100644 --- a/src/connector/src/source/kinesis/enumerator/client.rs +++ b/src/connector/src/source/kinesis/enumerator/client.rs @@ -19,7 +19,7 @@ use aws_sdk_kinesis::Client as kinesis_client; use risingwave_common::bail; use crate::error::ConnectorResult as Result; -use crate::source::kinesis::split::{KinesisOffset, KinesisSplit}; +use crate::source::kinesis::split::KinesisOffset; use crate::source::kinesis::*; use crate::source::{SourceEnumeratorContextRef, SplitEnumerator}; diff --git a/src/connector/src/source/kinesis/mod.rs b/src/connector/src/source/kinesis/mod.rs index 019cefb500643..de393a5121961 100644 --- a/src/connector/src/source/kinesis/mod.rs +++ b/src/connector/src/source/kinesis/mod.rs @@ -65,8 +65,6 @@ impl crate::source::UnknownFields for KinesisProperties { #[cfg(test)] mod test { - use std::collections::HashMap; - use maplit::hashmap; use super::*; diff --git a/src/connector/src/source/kinesis/source/message.rs b/src/connector/src/source/kinesis/source/message.rs index 51a3a9f2ed1ec..c12679437c1f2 100644 --- a/src/connector/src/source/kinesis/source/message.rs +++ b/src/connector/src/source/kinesis/source/message.rs @@ -17,12 +17,10 @@ use aws_smithy_types_convert::date_time::DateTimeExt; use crate::source::{SourceMessage, SourceMeta, SplitId}; -#[derive(Clone, Debug)] -pub struct KinesisMessage {} - #[derive(Clone, Debug)] pub struct KinesisMeta { // from `approximate_arrival_timestamp` of type `Option` + #[expect(dead_code)] timestamp: Option, } diff --git a/src/connector/src/source/kinesis/source/reader.rs b/src/connector/src/source/kinesis/source/reader.rs index c9026428d1df0..b728270dbd821 100644 --- a/src/connector/src/source/kinesis/source/reader.rs +++ b/src/connector/src/source/kinesis/source/reader.rs @@ -24,7 +24,6 @@ use aws_sdk_kinesis::Client as KinesisClient; use futures_async_stream::try_stream; use risingwave_common::bail; use thiserror_ext::AsReport; -use tokio_retry; use crate::error::ConnectorResult as Result; use crate::parser::ParserConfig; @@ -32,8 +31,8 @@ use crate::source::kinesis::source::message::from_kinesis_record; use crate::source::kinesis::split::{KinesisOffset, KinesisSplit}; use crate::source::kinesis::KinesisProperties; use crate::source::{ - into_chunk_stream, BoxChunkSourceStream, Column, CommonSplitReader, SourceContextRef, - SourceMessage, SplitId, SplitMetaData, SplitReader, + into_chunk_stream, BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SplitId, + SplitMetaData, SplitReader, }; #[derive(Debug, Clone)] @@ -44,6 +43,7 @@ pub struct KinesisSplitReader { latest_offset: Option, shard_iter: Option, start_position: KinesisOffset, + #[expect(dead_code)] end_position: KinesisOffset, split_id: SplitId, @@ -115,11 +115,11 @@ impl SplitReader for KinesisSplitReader { fn into_stream(self) -> BoxChunkSourceStream { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } -impl CommonSplitReader for KinesisSplitReader { +impl KinesisSplitReader { #[try_stream(ok = Vec < SourceMessage >, error = crate::error::ConnectorError)] async fn into_data_stream(mut self) { self.new_shard_iter().await?; @@ -306,7 +306,6 @@ mod tests { use super::*; use crate::connector_common::KinesisCommon; - use crate::source::kinesis::split::KinesisSplit; use crate::source::SourceContext; #[tokio::test] diff --git a/src/connector/src/source/manager.rs b/src/connector/src/source/manager.rs index a5584f6af83d5..731d0c4ff8ae8 100644 --- a/src/connector/src/source/manager.rs +++ b/src/connector/src/source/manager.rs @@ -21,26 +21,28 @@ use risingwave_common::catalog::{ use risingwave_common::types::DataType; use risingwave_pb::plan_common::{AdditionalColumn, ColumnDescVersion}; -/// `SourceColumnDesc` is used to describe a column in the Source and is used as the column -/// counterpart in `StreamScan` +/// `SourceColumnDesc` is used to describe a column in the Source. +/// +/// See the implementation of `From<&ColumnDesc>` for the difference between `SourceColumnDesc` and [`ColumnDesc`]. #[derive(Clone, Debug)] pub struct SourceColumnDesc { pub name: String, pub data_type: DataType, pub column_id: ColumnId, pub fields: Vec, + /// `additional_column` and `column_type` are orthogonal + /// `additional_column` is used to indicate the column is from which part of the message + /// `column_type` is used to indicate the type of the column, only used in cdc scenario + pub additional_column: AdditionalColumn, + // ------ + // Fields above are the same in `ColumnDesc`. + // Fields below are specific to `SourceColumnDesc`. + // ------ pub column_type: SourceColumnType, - /// `is_pk` is used to indicate whether the column is part of the primary key columns. pub is_pk: bool, - /// `is_hidden_addition_col` is used to indicate whether the column is a hidden addition column. pub is_hidden_addition_col: bool, - - /// `additional_column` and `column_type` are orthogonal - /// `additional_column` is used to indicate the column is from which part of the message - /// `column_type` is used to indicate the type of the column, only used in cdc scenario - pub additional_column: AdditionalColumn, } /// `SourceColumnType` is used to indicate the type of a column emitted by the Source. @@ -121,32 +123,63 @@ impl SourceColumnDesc { } impl From<&ColumnDesc> for SourceColumnDesc { - fn from(c: &ColumnDesc) -> Self { - let column_type = SourceColumnType::from_name(c.name.as_str()); + fn from( + ColumnDesc { + data_type, + column_id, + name, + field_descs, + additional_column, + // ignored fields below + generated_or_default_column, + type_name: _, + description: _, + version: _, + }: &ColumnDesc, + ) -> Self { + debug_assert!( + generated_or_default_column.is_none(), + "source column should not be generated or default: {:?}", + generated_or_default_column.as_ref().unwrap() + ); Self { - name: c.name.clone(), - data_type: c.data_type.clone(), - column_id: c.column_id, - fields: c.field_descs.clone(), - column_type, + name: name.clone(), + data_type: data_type.clone(), + column_id: *column_id, + fields: field_descs.clone(), + additional_column: additional_column.clone(), + // additional fields below + column_type: SourceColumnType::from_name(name), is_pk: false, is_hidden_addition_col: false, - additional_column: c.additional_column.clone(), } } } impl From<&SourceColumnDesc> for ColumnDesc { - fn from(s: &SourceColumnDesc) -> Self { + fn from( + SourceColumnDesc { + name, + data_type, + column_id, + fields, + additional_column, + // ignored fields below + column_type: _, + is_pk: _, + is_hidden_addition_col: _, + }: &SourceColumnDesc, + ) -> Self { ColumnDesc { - data_type: s.data_type.clone(), - column_id: s.column_id, - name: s.name.clone(), - field_descs: s.fields.clone(), + data_type: data_type.clone(), + column_id: *column_id, + name: name.clone(), + field_descs: fields.clone(), + additional_column: additional_column.clone(), + // additional fields below type_name: "".to_string(), generated_or_default_column: None, description: None, - additional_column: s.additional_column.clone(), version: ColumnDescVersion::Pr13707, } } diff --git a/src/connector/src/source/mod.rs b/src/connector/src/source/mod.rs index f965d373d9306..ed8842e70825f 100644 --- a/src/connector/src/source/mod.rs +++ b/src/connector/src/source/mod.rs @@ -25,8 +25,10 @@ pub mod mqtt; pub mod nats; pub mod nexmark; pub mod pulsar; + pub use base::{UPSTREAM_SOURCE_KEY, *}; pub(crate) use common::*; +use google_cloud_pubsub::subscription::Subscription; pub use google_pubsub::GOOGLE_PUBSUB_CONNECTOR; pub use kafka::KAFKA_CONNECTOR; pub use kinesis::KINESIS_CONNECTOR; @@ -39,6 +41,8 @@ pub mod reader; pub mod test_source; pub use manager::{SourceColumnDesc, SourceColumnType}; +use risingwave_common::array::{Array, ArrayRef}; +use thiserror_ext::AsReport; pub use crate::source::filesystem::opendal_source::{ GCS_CONNECTOR, OPENDAL_S3_CONNECTOR, POSIX_FS_CONNECTOR, @@ -68,3 +72,57 @@ pub fn should_copy_to_format_encode_options(key: &str, connector: &str) -> bool PREFIXES.iter().any(|prefix| key.starts_with(prefix)) || (key == "endpoint" && !connector.eq_ignore_ascii_case(KINESIS_CONNECTOR)) } + +/// Tasks executed by `WaitCheckpointWorker` +pub enum WaitCheckpointTask { + CommitCdcOffset(Option<(SplitId, String)>), + AckPubsubMessage(Subscription, Vec), +} + +impl WaitCheckpointTask { + pub async fn run(self) { + use std::str::FromStr; + match self { + WaitCheckpointTask::CommitCdcOffset(updated_offset) => { + if let Some((split_id, offset)) = updated_offset { + let source_id: u64 = u64::from_str(split_id.as_ref()).unwrap(); + // notify cdc connector to commit offset + match cdc::jni_source::commit_cdc_offset(source_id, offset.clone()) { + Ok(()) => {} + Err(e) => { + tracing::error!( + error = %e.as_report(), + "source#{source_id}: failed to commit cdc offset: {offset}.", + ) + } + } + } + } + WaitCheckpointTask::AckPubsubMessage(subscription, ack_id_arrs) => { + async fn ack(subscription: &Subscription, ack_ids: Vec) { + tracing::trace!("acking pubsub messages {:?}", ack_ids); + match subscription.ack(ack_ids).await { + Ok(()) => {} + Err(e) => { + tracing::error!( + error = %e.as_report(), + "failed to ack pubsub messages", + ) + } + } + } + const MAX_ACK_BATCH_SIZE: usize = 1000; + let mut ack_ids: Vec = vec![]; + for arr in ack_id_arrs { + for ack_id in arr.as_utf8().iter().flatten() { + ack_ids.push(ack_id.to_string()); + if ack_ids.len() >= MAX_ACK_BATCH_SIZE { + ack(&subscription, std::mem::take(&mut ack_ids)).await; + } + } + } + ack(&subscription, ack_ids).await; + } + } + } +} diff --git a/src/connector/src/source/mqtt/enumerator/mod.rs b/src/connector/src/source/mqtt/enumerator/mod.rs index 8d4dc636cd00a..f2b949fca17b6 100644 --- a/src/connector/src/source/mqtt/enumerator/mod.rs +++ b/src/connector/src/source/mqtt/enumerator/mod.rs @@ -29,7 +29,9 @@ use crate::error::ConnectorResult; use crate::source::{SourceEnumeratorContextRef, SplitEnumerator}; pub struct MqttSplitEnumerator { + #[expect(dead_code)] topic: String, + #[expect(dead_code)] client: rumqttc::v5::AsyncClient, topics: Arc>>, connected: Arc, diff --git a/src/connector/src/source/mqtt/source/reader.rs b/src/connector/src/source/mqtt/source/reader.rs index 50f90c816390c..2c57c8b9966b1 100644 --- a/src/connector/src/source/mqtt/source/reader.rs +++ b/src/connector/src/source/mqtt/source/reader.rs @@ -23,7 +23,7 @@ use super::message::MqttMessage; use super::MqttSplit; use crate::error::ConnectorResult as Result; use crate::parser::ParserConfig; -use crate::source::common::{into_chunk_stream, CommonSplitReader}; +use crate::source::common::into_chunk_stream; use crate::source::mqtt::MqttProperties; use crate::source::{BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SplitReader}; @@ -32,6 +32,7 @@ pub struct MqttSplitReader { client: rumqttc::v5::AsyncClient, qos: QoS, splits: Vec, + #[expect(dead_code)] properties: MqttProperties, parser_config: ParserConfig, source_ctx: SourceContextRef, @@ -78,11 +79,11 @@ impl SplitReader for MqttSplitReader { fn into_stream(self) -> BoxChunkSourceStream { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } -impl CommonSplitReader for MqttSplitReader { +impl MqttSplitReader { #[try_stream(ok = Vec, error = crate::error::ConnectorError)] async fn into_data_stream(self) { let mut eventloop = self.eventloop; diff --git a/src/connector/src/source/nats/enumerator/mod.rs b/src/connector/src/source/nats/enumerator/mod.rs index 557921747b8f0..29c4f93ef603f 100644 --- a/src/connector/src/source/nats/enumerator/mod.rs +++ b/src/connector/src/source/nats/enumerator/mod.rs @@ -25,6 +25,7 @@ use crate::source::{SourceEnumeratorContextRef, SplitEnumerator, SplitId}; #[derive(Debug, Clone)] pub struct NatsSplitEnumerator { subject: String, + #[expect(dead_code)] split_id: SplitId, client: async_nats::Client, } diff --git a/src/connector/src/source/nats/source/message.rs b/src/connector/src/source/nats/source/message.rs index 3944cd6bd4c71..55c91457d4bfc 100644 --- a/src/connector/src/source/nats/source/message.rs +++ b/src/connector/src/source/nats/source/message.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use async_nats; use async_nats::jetstream::Message; use crate::source::base::SourceMessage; diff --git a/src/connector/src/source/nats/source/reader.rs b/src/connector/src/source/nats/source/reader.rs index 05954538b91cf..916378a263979 100644 --- a/src/connector/src/source/nats/source/reader.rs +++ b/src/connector/src/source/nats/source/reader.rs @@ -23,7 +23,7 @@ use super::message::NatsMessage; use super::{NatsOffset, NatsSplit}; use crate::error::ConnectorResult as Result; use crate::parser::ParserConfig; -use crate::source::common::{into_chunk_stream, CommonSplitReader}; +use crate::source::common::into_chunk_stream; use crate::source::nats::NatsProperties; use crate::source::{ BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SplitId, SplitReader, @@ -31,9 +31,11 @@ use crate::source::{ pub struct NatsSplitReader { consumer: consumer::Consumer, + #[expect(dead_code)] properties: NatsProperties, parser_config: ParserConfig, source_ctx: SourceContextRef, + #[expect(dead_code)] start_position: NatsOffset, split_id: SplitId, } @@ -98,11 +100,11 @@ impl SplitReader for NatsSplitReader { fn into_stream(self) -> BoxChunkSourceStream { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } -impl CommonSplitReader for NatsSplitReader { +impl NatsSplitReader { #[try_stream(ok = Vec, error = crate::error::ConnectorError)] async fn into_data_stream(self) { let capacity = self.source_ctx.source_ctrl_opts.chunk_size; diff --git a/src/connector/src/source/nexmark/source/reader.rs b/src/connector/src/source/nexmark/source/reader.rs index 6441baa154ae4..aee7c178d116a 100644 --- a/src/connector/src/source/nexmark/source/reader.rs +++ b/src/connector/src/source/nexmark/source/reader.rs @@ -41,6 +41,7 @@ use crate::source::{ #[derive(Debug)] pub struct NexmarkSplitReader { generator: EventGenerator, + #[expect(dead_code)] assigned_split: NexmarkSplit, event_num: u64, event_type: Option, @@ -210,7 +211,7 @@ impl NexmarkSplitReader { #[cfg(test)] mod tests { use super::*; - use crate::source::nexmark::{NexmarkProperties, NexmarkSplitEnumerator}; + use crate::source::nexmark::NexmarkSplitEnumerator; use crate::source::{SourceContext, SourceEnumeratorContext, SplitEnumerator}; #[tokio::test] diff --git a/src/connector/src/source/pulsar/source/reader.rs b/src/connector/src/source/pulsar/source/reader.rs index ce808b96200cf..212c459388b25 100644 --- a/src/connector/src/source/pulsar/source/reader.rs +++ b/src/connector/src/source/pulsar/source/reader.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; use anyhow::Context; -use arrow_array::{Int32Array, Int64Array, RecordBatch}; +use arrow_array_iceberg::{Int32Array, Int64Array, RecordBatch}; use async_trait::async_trait; use futures::StreamExt; use futures_async_stream::try_stream; @@ -27,7 +27,8 @@ use itertools::Itertools; use pulsar::consumer::InitialPosition; use pulsar::message::proto::MessageIdData; use pulsar::{Consumer, ConsumerBuilder, ConsumerOptions, Pulsar, SubType, TokioExecutor}; -use risingwave_common::array::{DataChunk, StreamChunk}; +use risingwave_common::array::arrow::IcebergArrowConvert; +use risingwave_common::array::StreamChunk; use risingwave_common::catalog::ROWID_PREFIX; use risingwave_common::{bail, ensure}; use thiserror_ext::AsReport; @@ -37,8 +38,8 @@ use crate::parser::ParserConfig; use crate::source::pulsar::split::PulsarSplit; use crate::source::pulsar::{PulsarEnumeratorOffset, PulsarProperties}; use crate::source::{ - into_chunk_stream, BoxChunkSourceStream, Column, CommonSplitReader, SourceContextRef, - SourceMessage, SplitId, SplitMetaData, SplitReader, + into_chunk_stream, BoxChunkSourceStream, Column, SourceContextRef, SourceMessage, SplitId, + SplitMetaData, SplitReader, }; pub enum PulsarSplitReader { @@ -88,7 +89,11 @@ impl SplitReader for PulsarSplitReader { Self::Broker(reader) => { let (parser_config, source_context) = (reader.parser_config.clone(), reader.source_ctx.clone()); - Box::pin(into_chunk_stream(reader, parser_config, source_context)) + Box::pin(into_chunk_stream( + reader.into_data_stream(), + parser_config, + source_context, + )) } Self::Iceberg(reader) => Box::pin(reader.into_stream()), } @@ -97,10 +102,12 @@ impl SplitReader for PulsarSplitReader { /// This reader reads from pulsar broker pub struct PulsarBrokerReader { + #[expect(dead_code)] pulsar: Pulsar, consumer: Consumer, TokioExecutor>, + #[expect(dead_code)] split: PulsarSplit, - + #[expect(dead_code)] split_id: SplitId, parser_config: ParserConfig, source_ctx: SourceContextRef, @@ -180,12 +187,16 @@ impl SplitReader for PulsarBrokerReader { ) } else { builder.with_options( - ConsumerOptions::default().with_initial_position(InitialPosition::Earliest), + ConsumerOptions::default() + .with_initial_position(InitialPosition::Earliest) + .durable(false), ) } } PulsarEnumeratorOffset::Latest => builder.with_options( - ConsumerOptions::default().with_initial_position(InitialPosition::Latest), + ConsumerOptions::default() + .with_initial_position(InitialPosition::Latest) + .durable(false), ), PulsarEnumeratorOffset::MessageId(m) => { if topic.starts_with("non-persistent://") { @@ -226,11 +237,11 @@ impl SplitReader for PulsarBrokerReader { fn into_stream(self) -> BoxChunkSourceStream { let parser_config = self.parser_config.clone(); let source_context = self.source_ctx.clone(); - into_chunk_stream(self, parser_config, source_context) + into_chunk_stream(self.into_data_stream(), parser_config, source_context) } } -impl CommonSplitReader for PulsarBrokerReader { +impl PulsarBrokerReader { #[try_stream(ok = Vec, error = crate::error::ConnectorError)] async fn into_data_stream(self) { let max_chunk_size = self.source_ctx.source_ctrl_opts.chunk_size; @@ -246,7 +257,9 @@ impl CommonSplitReader for PulsarBrokerReader { } } +#[expect(dead_code)] const META_COLUMN_TOPIC: &str = "__topic"; +#[expect(dead_code)] const META_COLUMN_KEY: &str = "__key"; const META_COLUMN_LEDGER_ID: &str = "__ledgerId"; const META_COLUMN_ENTRY_ID: &str = "__entryId"; @@ -508,7 +521,8 @@ impl PulsarIcebergReader { offsets.push(offset); } - let data_chunk = DataChunk::try_from(&record_batch.project(&field_indices)?) + let data_chunk = IcebergArrowConvert + .chunk_from_record_batch(&record_batch.project(&field_indices)?) .context("failed to convert arrow record batch to data chunk")?; let stream_chunk = StreamChunk::from(data_chunk); diff --git a/src/connector/src/source/pulsar/topic.rs b/src/connector/src/source/pulsar/topic.rs index 352c7e47d8da2..f4cb95bd01437 100644 --- a/src/connector/src/source/pulsar/topic.rs +++ b/src/connector/src/source/pulsar/topic.rs @@ -108,6 +108,7 @@ pub fn get_partition_index(topic: &str) -> Result> { /// The short topic name can be: /// - `` /// - `//` +/// /// The fully qualified topic name can be: /// `:////` pub fn parse_topic(topic: &str) -> Result { diff --git a/src/connector/src/source/reader/desc.rs b/src/connector/src/source/reader/desc.rs index 808ef1232c50c..44d2effe51606 100644 --- a/src/connector/src/source/reader/desc.rs +++ b/src/connector/src/source/reader/desc.rs @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use std::sync::Arc; -use itertools::Itertools; use risingwave_common::bail; -use risingwave_common::catalog::{ColumnCatalog, ColumnDesc}; +use risingwave_common::catalog::ColumnCatalog; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_pb::catalog::PbStreamSourceInfo; use risingwave_pb::plan_common::PbColumnCatalog; @@ -26,11 +25,10 @@ use risingwave_pb::plan_common::PbColumnCatalog; use super::fs_reader::FsSourceReader; use super::reader::SourceReader; use crate::error::ConnectorResult; -use crate::parser::additional_columns::add_partition_offset_cols; +use crate::parser::additional_columns::source_add_partition_offset_cols; use crate::parser::{EncodingProperties, ProtocolProperties, SpecificParserConfig}; use crate::source::monitor::SourceMetrics; use crate::source::{SourceColumnDesc, SourceColumnType, UPSTREAM_SOURCE_KEY}; -use crate::ConnectorParams; pub const DEFAULT_CONNECTOR_MESSAGE_BUFFER_SIZE: usize = 16; @@ -54,35 +52,31 @@ pub struct FsSourceDesc { #[derive(Clone)] pub struct SourceDescBuilder { - columns: Vec, + columns: Vec, metrics: Arc, row_id_index: Option, - with_properties: HashMap, + with_properties: BTreeMap, source_info: PbStreamSourceInfo, - connector_params: ConnectorParams, connector_message_buffer_size: usize, pk_indices: Vec, } impl SourceDescBuilder { - #[allow(clippy::too_many_arguments)] pub fn new( columns: Vec, metrics: Arc, row_id_index: Option, - with_properties: HashMap, + with_properties: BTreeMap, source_info: PbStreamSourceInfo, - connector_params: ConnectorParams, connector_message_buffer_size: usize, pk_indices: Vec, ) -> Self { Self { - columns, + columns: columns.into_iter().map(ColumnCatalog::from).collect(), metrics, row_id_index, with_properties, source_info, - connector_params, connector_message_buffer_size, pk_indices, } @@ -96,25 +90,18 @@ impl SourceDescBuilder { .get(UPSTREAM_SOURCE_KEY) .map(|s| s.to_lowercase()) .unwrap(); - let columns = self - .columns - .iter() - .map(|c| ColumnCatalog::from(c.clone())) - .collect_vec(); let (columns_exist, additional_columns) = - add_partition_offset_cols(&columns, &connector_name); + source_add_partition_offset_cols(&self.columns, &connector_name); let mut columns: Vec<_> = self .columns .iter() - .map(|c| SourceColumnDesc::from(&ColumnDesc::from(c.column_desc.as_ref().unwrap()))) + .map(|c| SourceColumnDesc::from(&c.column_desc)) .collect(); for (existed, c) in columns_exist.iter().zip_eq_fast(&additional_columns) { if !existed { - columns.push(SourceColumnDesc::hidden_addition_col_from_column_desc( - &c.column_desc, - )); + columns.push(SourceColumnDesc::hidden_addition_col_from_column_desc(c)); } } @@ -186,11 +173,10 @@ impl SourceDescBuilder { } pub mod test_utils { - use std::collections::HashMap; + use std::collections::BTreeMap; - use risingwave_common::catalog::{ColumnDesc, Schema}; + use risingwave_common::catalog::{ColumnCatalog, ColumnDesc, Schema}; use risingwave_pb::catalog::StreamSourceInfo; - use risingwave_pb::plan_common::ColumnCatalog; use super::{SourceDescBuilder, DEFAULT_CONNECTOR_MESSAGE_BUFFER_SIZE}; @@ -198,23 +184,19 @@ pub mod test_utils { schema: &Schema, row_id_index: Option, source_info: StreamSourceInfo, - with_properties: HashMap, + with_properties: BTreeMap, pk_indices: Vec, ) -> SourceDescBuilder { let columns = schema .fields .iter() .enumerate() - .map(|(i, f)| ColumnCatalog { - column_desc: Some( - ColumnDesc::named( - f.name.clone(), - (i as i32).into(), // use column index as column id - f.data_type.clone(), - ) - .to_protobuf(), - ), - is_hidden: false, + .map(|(i, f)| { + ColumnCatalog::visible(ColumnDesc::named( + f.name.clone(), + (i as i32).into(), // use column index as column id + f.data_type.clone(), + )) }) .collect(); SourceDescBuilder { @@ -223,7 +205,6 @@ pub mod test_utils { row_id_index, with_properties, source_info, - connector_params: Default::default(), connector_message_buffer_size: DEFAULT_CONNECTOR_MESSAGE_BUFFER_SIZE, pk_indices, } diff --git a/src/connector/src/source/reader/fs_reader.rs b/src/connector/src/source/reader/fs_reader.rs index 5ec47cea98cee..5bd139e70983d 100644 --- a/src/connector/src/source/reader/fs_reader.rs +++ b/src/connector/src/source/reader/fs_reader.rs @@ -14,7 +14,7 @@ #![deprecated = "will be replaced by new fs source (list + fetch)"] -use std::collections::HashMap; +use std::collections::BTreeMap; use std::sync::Arc; use anyhow::Context; @@ -34,20 +34,19 @@ use crate::source::{ pub struct FsSourceReader { pub config: ConnectorProperties, pub columns: Vec, - pub properties: HashMap, + pub properties: BTreeMap, pub parser_config: SpecificParserConfig, } impl FsSourceReader { #[allow(clippy::too_many_arguments)] pub fn new( - properties: HashMap, + properties: BTreeMap, columns: Vec, parser_config: SpecificParserConfig, ) -> ConnectorResult { // Store the connector node address to properties for later use. - let source_props: HashMap = HashMap::from_iter(properties.clone()); - let config = ConnectorProperties::extract(source_props, false)?; + let config = ConnectorProperties::extract(properties.clone(), false)?; Ok(Self { config, diff --git a/src/connector/src/source/reader/reader.rs b/src/connector/src/source/reader/reader.rs index e8c20f66dedc4..02012841c5a48 100644 --- a/src/connector/src/source/reader/reader.rs +++ b/src/connector/src/source/reader/reader.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use std::sync::Arc; use anyhow::Context; @@ -36,7 +36,7 @@ use crate::source::filesystem::opendal_source::{ use crate::source::filesystem::{FsPageItem, OpendalFsSplit}; use crate::source::{ create_split_reader, BoxChunkSourceStream, BoxTryStream, Column, ConnectorProperties, - ConnectorState, SourceColumnDesc, SourceContext, SplitReader, + ConnectorState, SourceColumnDesc, SourceContext, SplitReader, WaitCheckpointTask, }; #[derive(Clone, Debug)] @@ -49,7 +49,7 @@ pub struct SourceReader { impl SourceReader { pub fn new( - properties: HashMap, + properties: BTreeMap, columns: Vec, connector_message_buffer_size: usize, parser_config: SpecificParserConfig, @@ -105,18 +105,22 @@ impl SourceReader { } } - /// Postgres and Oracle connectors need to commit the offset to upstream. - pub fn need_commit_offset_to_upstream(&self) -> bool { - matches!( - &self.config, - ConnectorProperties::PostgresCdc(_) - | ConnectorProperties::MysqlCdc(_) - | ConnectorProperties::MongodbCdc(_) - | ConnectorProperties::CitusCdc(_) - ) + /// Refer to `WaitCheckpointWorker` for more details. + pub async fn create_wait_checkpoint_task(&self) -> ConnectorResult> { + Ok(match &self.config { + ConnectorProperties::PostgresCdc(_prop) => { + Some(WaitCheckpointTask::CommitCdcOffset(None)) + } + ConnectorProperties::GooglePubsub(prop) => Some(WaitCheckpointTask::AckPubsubMessage( + prop.subscription_client().await?, + vec![], + )), + _ => None, + }) } - pub async fn to_stream( + /// Build `SplitReader`s and then `BoxChunkSourceStream` from the given `ConnectorState` (`SplitImpl`s). + pub async fn build_stream( &self, state: ConnectorState, column_ids: Vec, @@ -161,7 +165,7 @@ impl SourceReader { } else { let to_reader_splits = splits.into_iter().map(|split| vec![split]); try_join_all(to_reader_splits.into_iter().map(|splits| { - tracing::debug!(?splits, ?prop, "spawning connector split reader"); + tracing::debug!(?splits, "spawning connector split reader"); let props = prop.clone(); let data_gen_columns = data_gen_columns.clone(); let parser_config = parser_config.clone(); diff --git a/src/connector/src/source/test_source.rs b/src/connector/src/source/test_source.rs index e8379fafeeb71..57f659873936b 100644 --- a/src/connector/src/source/test_source.rs +++ b/src/connector/src/source/test_source.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use std::sync::{Arc, OnceLock}; use async_trait::async_trait; @@ -26,7 +26,7 @@ use crate::error::ConnectorResult; use crate::parser::ParserConfig; use crate::source::{ BoxChunkSourceStream, Column, SourceContextRef, SourceEnumeratorContextRef, SourceProperties, - SplitEnumerator, SplitId, SplitMetaData, SplitReader, TryFromHashmap, + SplitEnumerator, SplitId, SplitMetaData, SplitReader, TryFromBTreeMap, }; pub type BoxListSplits = Box< @@ -118,12 +118,12 @@ pub const TEST_CONNECTOR: &str = "test"; #[derive(Clone, Debug, Default, WithOptions)] pub struct TestSourceProperties { - properties: HashMap, + properties: BTreeMap, } -impl TryFromHashmap for TestSourceProperties { - fn try_from_hashmap( - props: HashMap, +impl TryFromBTreeMap for TestSourceProperties { + fn try_from_btreemap( + props: BTreeMap, _deny_unknown_fields: bool, ) -> ConnectorResult { if cfg!(any(madsim, test)) { diff --git a/src/connector/src/test_data/union-schema.avsc b/src/connector/src/test_data/union-schema.avsc deleted file mode 100644 index 4176d8f771f09..0000000000000 --- a/src/connector/src/test_data/union-schema.avsc +++ /dev/null @@ -1,88 +0,0 @@ -{ - "name": "test_student", - "type": "record", - "fields": [ - { - "name": "id", - "type": "int", - "default": 0 - }, - { - "name": "age", - "type": ["null", "int"] - }, - { - "name": "sequence_id", - "type": ["null", "long"] - }, - { - "name": "name", - "type": ["null", "string"], - "default": null - }, - { - "name": "score", - "type": [ "float", "null" ], - "default": 1.0 - }, - { - "name": "avg_score", - "type": ["null", "double"] - }, - { - "name": "is_lasted", - "type": ["null", "boolean"] - }, - { - "name": "entrance_date", - "type": [ - "null", - { - "type": "int", - "logicalType": "date", - "arg.properties": { - "range": { - "min": 1, - "max": 19374 - } - } - } - ], - "default": null - }, - { - "name": "birthday", - "type": [ - "null", - { - "type": "long", - "logicalType": "timestamp-millis", - "arg.properties": { - "range": { - "min": 1, - "max": 1673970376213 - } - } - } - ], - "default": null - }, - { - "name": "anniversary", - "type": [ - "null", - { - "type" : "long", - "logicalType": "timestamp-micros", - "arg.properties": { - "range": { - "min": 1, - "max": 1673970376213000 - } - } - } - ], - "default": null - } - ] -} \ No newline at end of file diff --git a/src/connector/src/with_options.rs b/src/connector/src/with_options.rs index 5b7e75a47bcd6..154586d770522 100644 --- a/src/connector/src/with_options.rs +++ b/src/connector/src/with_options.rs @@ -48,10 +48,12 @@ impl WithOptions impl WithOptions for Option {} impl WithOptions for Vec {} impl WithOptions for HashMap {} +impl WithOptions for BTreeMap {} impl WithOptions for String {} impl WithOptions for bool {} impl WithOptions for usize {} +impl WithOptions for u16 {} impl WithOptions for u32 {} impl WithOptions for u64 {} impl WithOptions for i32 {} @@ -60,6 +62,7 @@ impl WithOptions for f64 {} impl WithOptions for std::time::Duration {} impl WithOptions for crate::connector_common::mqtt_common::QualityOfService {} impl WithOptions for crate::sink::kafka::CompressionCodec {} +impl WithOptions for crate::source::filesystem::file_common::CompressionFormat {} impl WithOptions for nexmark::config::RateShape {} impl WithOptions for nexmark::event::EventType {} diff --git a/src/connector/src/with_options_test.rs b/src/connector/src/with_options_test.rs index 155932ce90452..cd2adc11d0718 100644 --- a/src/connector/src/with_options_test.rs +++ b/src/connector/src/with_options_test.rs @@ -63,7 +63,7 @@ pub fn generate_with_options_yaml_sink() -> String { /// the generated `yaml` might be inconsistent with the actual parsing logic. /// TODO: improve the test to check whether serde is used. /// -/// - For sources, the parsing logic is in `TryFromHashMap`. +/// - For sources, the parsing logic is in `TryFromBTreeMap`. /// - For sinks, the parsing logic is in `TryFrom`. fn generate_with_options_yaml_inner(path: &Path) -> String { let mut structs = vec![]; @@ -191,15 +191,15 @@ struct FieldInfo { #[serde(skip_serializing_if = "Option::is_none")] default: Option, - #[serde(skip_serializing_if = "Option::is_none")] - alias: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + alias: Vec, } #[derive(Default)] struct SerdeProperties { default_func: Option, rename: Option, - alias: Option, + alias: Vec, } #[derive(Debug, Serialize, Default)] @@ -270,7 +270,7 @@ fn extract_serde_properties(field: &Field) -> SerdeProperties { } } else if path.is_ident("alias") { if let Lit::Str(lit_str) = lit { - serde_props.alias = Some(lit_str.value()); + serde_props.alias.push(lit_str.value()); } } else if path.is_ident("default") { if let Lit::Str(lit_str) = lit { diff --git a/src/connector/with_options_sink.yaml b/src/connector/with_options_sink.yaml index 27daa718b64f9..27362900dd054 100644 --- a/src/connector/with_options_sink.yaml +++ b/src/connector/with_options_sink.yaml @@ -21,32 +21,57 @@ BigQueryConfig: field_type: usize required: false default: '1024' - - name: region + - name: bigquery.retry_times + field_type: usize + required: false + default: '5' + - name: auto_create + field_type: bool + required: false + default: Default::default + - name: aws.region field_type: String required: false - - name: endpoint + alias: + - region + - name: aws.endpoint_url field_type: String required: false - alias: endpoint_url - - name: access_key + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id field_type: String required: false - - name: secret_key + alias: + - access_key + - name: aws.credentials.secret_access_key field_type: String required: false - - name: session_token + alias: + - secret_key + - name: aws.credentials.session_token field_type: String required: false - - name: arn + alias: + - session_token + - name: aws.credentials.role.arn field_type: String + comments: IAM role required: false - - name: external_id + alias: + - arn + - name: aws.credentials.role.external_id field_type: String - comments: This field was added for kinesis. Not sure if it's useful for other connectors. Please ignore it in the documentation for now. + comments: external ID in IAM role trust policy required: false - - name: profile + alias: + - external_id + - name: aws.profile field_type: String required: false + alias: + - profile - name: r#type field_type: String required: true @@ -67,6 +92,14 @@ ClickHouseConfig: - name: clickhouse.table field_type: String required: true + - name: clickhouse.delete.column + field_type: String + required: false + - name: commit_checkpoint_interval + field_type: u64 + comments: Commit every n(>0) checkpoints, if n is not set, we will commit every checkpoint. + required: false + default: Default::default - name: r#type field_type: String required: true @@ -90,6 +123,11 @@ DeltaLakeConfig: - name: gcs.service.account field_type: String required: false + - name: commit_checkpoint_interval + field_type: u64 + comments: Commit every n(>0) checkpoints, if n is not set, we will commit every checkpoint. + required: false + default: Default::default - name: r#type field_type: String required: true @@ -110,9 +148,88 @@ DorisConfig: - name: doris.table field_type: String required: true + - name: doris.partial_update + field_type: String + required: false - name: r#type field_type: String required: true +DynamoDbConfig: + fields: + - name: table + field_type: String + required: true + alias: + - dynamodb.table + - name: dynamodb.max_batch_rows + field_type: usize + required: false + default: '1024' + - name: aws.region + field_type: String + required: false + alias: + - region + - name: aws.endpoint_url + field_type: String + required: false + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id + field_type: String + required: false + alias: + - access_key + - name: aws.credentials.secret_access_key + field_type: String + required: false + alias: + - secret_key + - name: aws.credentials.session_token + field_type: String + required: false + alias: + - session_token + - name: aws.credentials.role.arn + field_type: String + comments: IAM role + required: false + alias: + - arn + - name: aws.credentials.role.external_id + field_type: String + comments: external ID in IAM role trust policy + required: false + alias: + - external_id + - name: aws.profile + field_type: String + required: false + alias: + - profile +GooglePubSubConfig: + fields: + - name: pubsub.project_id + field_type: String + comments: The Google Pub/Sub Project ID + required: true + - name: pubsub.topic + field_type: String + comments: Specifies the Pub/Sub topic to publish messages + required: true + - name: pubsub.endpoint + field_type: String + comments: The Google Pub/Sub endpoint URL + required: true + - name: pubsub.emulator_host + field_type: String + comments: use the connector with a pubsub emulator + required: false + - name: pubsub.credentials + field_type: String + comments: A JSON string containing the service account credentials for authorization, see the [service-account](https://developers.google.com/workspace/guides/create-credentials#create_credentials_for_a_service_account) credentials guide. The provided account credential must have the `pubsub.publisher` [role](https://cloud.google.com/pubsub/docs/access-control#roles) + required: false IcebergConfig: fields: - name: connector @@ -171,11 +288,13 @@ KafkaConfig: - name: properties.bootstrap.server field_type: String required: true - alias: kafka.brokers + alias: + - kafka.brokers - name: topic field_type: String required: true - alias: kafka.topic + alias: + - kafka.topic - name: properties.sync.call.timeout field_type: Duration required: false @@ -205,7 +324,7 @@ KafkaConfig: required: false - name: properties.sasl.mechanism field_type: String - comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM and GSSAPI. + comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM, GSSAPI, and AWS_MSK_IAM. required: false - name: properties.sasl.username field_type: String @@ -319,44 +438,125 @@ KafkaConfig: comments: The maximum number of unacknowledged requests the client will send on a single connection before blocking. required: false default: '5' + - name: properties.request.required.acks + field_type: i32 + required: false - name: broker.rewrite.endpoints - field_type: HashMap + field_type: BTreeMap comments: This is generated from `private_link_targets` and `private_link_endpoint` in frontend, instead of given by users. required: false + - name: aws.region + field_type: String + required: false + alias: + - region + - name: aws.endpoint_url + field_type: String + required: false + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id + field_type: String + required: false + alias: + - access_key + - name: aws.credentials.secret_access_key + field_type: String + required: false + alias: + - secret_key + - name: aws.credentials.session_token + field_type: String + required: false + alias: + - session_token + - name: aws.credentials.role.arn + field_type: String + comments: IAM role + required: false + alias: + - arn + - name: aws.credentials.role.external_id + field_type: String + comments: external ID in IAM role trust policy + required: false + alias: + - external_id + - name: aws.profile + field_type: String + required: false + alias: + - profile KinesisSinkConfig: fields: - name: stream field_type: String required: true - alias: kinesis.stream.name + alias: + - kinesis.stream.name - name: aws.region field_type: String required: true - alias: kinesis.stream.region + alias: + - kinesis.stream.region - name: endpoint field_type: String required: false - alias: kinesis.endpoint + alias: + - kinesis.endpoint - name: aws.credentials.access_key_id field_type: String required: false - alias: kinesis.credentials.access + alias: + - kinesis.credentials.access - name: aws.credentials.secret_access_key field_type: String required: false - alias: kinesis.credentials.secret + alias: + - kinesis.credentials.secret - name: aws.credentials.session_token field_type: String required: false - alias: kinesis.credentials.session_token + alias: + - kinesis.credentials.session_token - name: aws.credentials.role.arn field_type: String required: false - alias: kinesis.assumerole.arn + alias: + - kinesis.assumerole.arn - name: aws.credentials.role.external_id field_type: String required: false - alias: kinesis.assumerole.external_id + alias: + - kinesis.assumerole.external_id +MongodbConfig: + fields: + - name: mongodb.url + field_type: String + comments: The URL of MongoDB + required: true + - name: collection.name + field_type: String + comments: The collection name where data should be written to or read from. For sinks, the format is `db_name.collection_name`. Data can also be written to dynamic collections, see `collection.name.field` for more information. + required: true + - name: r#type + field_type: String + required: true + - name: collection.name.field + field_type: String + comments: The dynamic collection name where data should be sunk to. If specified, the field value will be used as the collection name. The collection name format is same as `collection.name`. If the field value is null or an empty string, then the `collection.name` will be used as a fallback destination. + required: false + - name: collection.name.field.drop + field_type: bool + comments: Controls whether the field value of `collection.name.field` should be dropped when sinking. Set this option to true to avoid the duplicate values of `collection.name.field` being written to the result collection. + required: false + default: Default::default + - name: mongodb.bulk_write.max_entries + field_type: usize + comments: The maximum entries will accumulate before performing the bulk write, defaults to 1024. + required: false + default: '1024' MqttConfig: fields: - name: url @@ -469,11 +669,13 @@ PulsarConfig: - name: topic field_type: String required: true - alias: pulsar.topic + alias: + - pulsar.topic - name: service.url field_type: String required: true - alias: pulsar.service.url + alias: + - pulsar.service.url - name: auth.token field_type: String required: false @@ -489,32 +691,49 @@ PulsarConfig: - name: oauth.scope field_type: String required: false - - name: region + - name: aws.region field_type: String required: false - - name: endpoint + alias: + - region + - name: aws.endpoint_url field_type: String required: false - alias: endpoint_url - - name: access_key + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id field_type: String required: false - - name: secret_key + alias: + - access_key + - name: aws.credentials.secret_access_key field_type: String required: false - - name: session_token + alias: + - secret_key + - name: aws.credentials.session_token field_type: String required: false - - name: arn + alias: + - session_token + - name: aws.credentials.role.arn field_type: String + comments: IAM role required: false - - name: external_id + alias: + - arn + - name: aws.credentials.role.external_id field_type: String - comments: This field was added for kinesis. Not sure if it's useful for other connectors. Please ignore it in the documentation for now. + comments: external ID in IAM role trust policy required: false - - name: profile + alias: + - external_id + - name: aws.profile field_type: String required: false + alias: + - profile - name: properties.batch.size field_type: u32 required: false @@ -578,6 +797,33 @@ SnowflakeConfig: field_type: String comments: The s3 region, e.g., us-east-2 required: true +SqlServerConfig: + fields: + - name: sqlserver.host + field_type: String + required: true + - name: sqlserver.port + field_type: u16 + required: true + - name: sqlserver.user + field_type: String + required: true + - name: sqlserver.password + field_type: String + required: true + - name: sqlserver.database + field_type: String + required: true + - name: sqlserver.table + field_type: String + required: true + - name: sqlserver.max_batch_rows + field_type: usize + required: false + default: '1024' + - name: r#type + field_type: String + required: true StarrocksConfig: fields: - name: starrocks.host @@ -588,12 +834,14 @@ StarrocksConfig: field_type: String comments: The port to the MySQL server of `StarRocks` FE. required: true - alias: starrocks.query_port + alias: + - starrocks.query_port - name: starrocks.httpport field_type: String comments: The port to the HTTP server of `StarRocks` FE. required: true - alias: starrocks.http_port + alias: + - starrocks.http_port - name: starrocks.user field_type: String comments: The user name used to access the `StarRocks` database. @@ -610,8 +858,19 @@ StarrocksConfig: field_type: String comments: The `StarRocks` table you want to sink data to. required: true + - name: starrocks.stream_load.http.timeout.ms + field_type: u64 + comments: The timeout in milliseconds for stream load http request, defaults to 10 seconds. + required: false + default: 30 * 1000 + - name: commit_checkpoint_interval + field_type: u64 + comments: Set this option to a positive integer n, RisingWave will try to commit data to Starrocks at every n checkpoints by leveraging the [StreamLoad Transaction API](https://docs.starrocks.io/docs/loading/Stream_Load_transaction_interface/), also, in this time, the `sink_decouple` option should be enabled as well. Defaults to 1 if commit_checkpoint_interval <= 0 + required: false + default: Default::default - name: starrocks.partial_update field_type: String + comments: Enable partial update required: false - name: r#type field_type: String diff --git a/src/connector/with_options_source.yaml b/src/connector/with_options_source.yaml index 2ea0e5f3488ee..8d808f526bf88 100644 --- a/src/connector/with_options_source.yaml +++ b/src/connector/with_options_source.yaml @@ -33,6 +33,10 @@ GcsProperties: field_type: String required: false default: Default::default + - name: compression_format + field_type: CompressionFormat + required: false + default: Default::default IcebergProperties: fields: - name: catalog.type @@ -44,15 +48,12 @@ IcebergProperties: - name: s3.endpoint field_type: String required: false - default: Default::default - name: s3.access.key field_type: String - required: false - default: Default::default + required: true - name: s3.secret.key field_type: String - required: false - default: Default::default + required: true - name: warehouse.path field_type: String required: true @@ -80,20 +81,25 @@ KafkaProperties: field_type: String comments: This parameter is not intended to be exposed to users. This parameter specifies only for one parallelism. The parallelism of kafka source is equal to the parallelism passed into compute nodes. So users need to calculate how many bytes will be consumed in total across all the parallelism by themselves. required: false - alias: kafka.bytes.per.second + alias: + - kafka.bytes.per.second - name: max.num.messages field_type: String comments: This parameter is not intended to be exposed to users. This parameter specifies only for one parallelism. The parallelism of kafka source is equal to the parallelism passed into compute nodes. So users need to calculate how many messages will be consumed in total across all the parallelism by themselves. required: false - alias: kafka.max.num.messages + alias: + - kafka.max.num.messages - name: scan.startup.mode field_type: String required: false - alias: kafka.scan.startup.mode + alias: + - kafka.scan.startup.mode - name: scan.startup.timestamp.millis field_type: String required: false - alias: scan.startup.timestamp_millis + alias: + - kafka.time.offset + - scan.startup.timestamp_millis - name: upsert field_type: String comments: 'This parameter is used to tell `KafkaSplitReader` to produce `UpsertMessage`s, which combine both key and value fields of the Kafka message. TODO: Currently, `Option` can not be parsed here.' @@ -101,11 +107,13 @@ KafkaProperties: - name: properties.bootstrap.server field_type: String required: true - alias: kafka.brokers + alias: + - kafka.brokers - name: topic field_type: String required: true - alias: kafka.topic + alias: + - kafka.topic - name: properties.sync.call.timeout field_type: Duration required: false @@ -135,7 +143,7 @@ KafkaProperties: required: false - name: properties.sasl.mechanism field_type: String - comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM and GSSAPI. + comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM, GSSAPI, and AWS_MSK_IAM. required: false - name: properties.sasl.username field_type: String @@ -211,50 +219,112 @@ KafkaProperties: comments: 'Whether to automatically and periodically commit offsets in the background. Note that RisingWave does NOT rely on committed offsets. Committing offset is only for exposing the progress for monitoring. Setting this to false can avoid creating consumer groups. default: true' required: false - name: broker.rewrite.endpoints - field_type: HashMap + field_type: BTreeMap comments: This is generated from `private_link_targets` and `private_link_endpoint` in frontend, instead of given by users. required: false + - name: aws.region + field_type: String + required: false + alias: + - region + - name: aws.endpoint_url + field_type: String + required: false + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id + field_type: String + required: false + alias: + - access_key + - name: aws.credentials.secret_access_key + field_type: String + required: false + alias: + - secret_key + - name: aws.credentials.session_token + field_type: String + required: false + alias: + - session_token + - name: aws.credentials.role.arn + field_type: String + comments: IAM role + required: false + alias: + - arn + - name: aws.credentials.role.external_id + field_type: String + comments: external ID in IAM role trust policy + required: false + alias: + - external_id + - name: aws.profile + field_type: String + required: false + alias: + - profile KinesisProperties: fields: - name: scan.startup.mode field_type: String required: false - alias: kinesis.scan.startup.mode + alias: + - kinesis.scan.startup.mode - name: scan.startup.timestamp.millis field_type: i64 required: false - name: stream field_type: String required: true - alias: kinesis.stream.name + alias: + - kinesis.stream.name - name: aws.region field_type: String required: true - alias: kinesis.stream.region + alias: + - kinesis.stream.region - name: endpoint field_type: String required: false - alias: kinesis.endpoint + alias: + - kinesis.endpoint - name: aws.credentials.access_key_id field_type: String required: false - alias: kinesis.credentials.access + alias: + - kinesis.credentials.access - name: aws.credentials.secret_access_key field_type: String required: false - alias: kinesis.credentials.secret + alias: + - kinesis.credentials.secret - name: aws.credentials.session_token field_type: String required: false - alias: kinesis.credentials.session_token + alias: + - kinesis.credentials.session_token - name: aws.credentials.role.arn field_type: String required: false - alias: kinesis.assumerole.arn + alias: + - kinesis.assumerole.arn - name: aws.credentials.role.external_id field_type: String required: false - alias: kinesis.assumerole.external_id + alias: + - kinesis.assumerole.external_id +MongodbCommon: + fields: + - name: mongodb.url + field_type: String + comments: The URL of MongoDB + required: true + - name: collection.name + field_type: String + comments: The collection name where data should be written to or read from. For sinks, the format is `db_name.collection_name`. Data can also be written to dynamic collections, see `collection.name.field` for more information. + required: true MqttProperties: fields: - name: url @@ -350,7 +420,8 @@ NatsProperties: - name: scan.startup.timestamp.millis field_type: String required: false - alias: scan.startup.timestamp_millis + alias: + - scan.startup.timestamp_millis - name: stream field_type: String required: true @@ -549,6 +620,10 @@ OpendalS3Properties: - name: s3.endpoint_url field_type: String required: false + - name: compression_format + field_type: CompressionFormat + required: false + default: Default::default - name: s3.assume_role field_type: String comments: The following are only supported by `s3_v2` (opendal) source. @@ -565,11 +640,15 @@ PosixFsProperties: comments: The regex pattern to match files under root directory. required: false default: Default::default + - name: compression_format + field_type: CompressionFormat + required: false + default: Default::default PubsubProperties: fields: - name: pubsub.subscription field_type: String - comments: pubsub subscription to consume messages from The subscription should be configured with the `retain-on-ack` property to enable message recovery within risingwave. + comments: 'Pub/Sub subscription to consume messages from. Note that we rely on Pub/Sub to load-balance messages between all Readers pulling from the same subscription. So one `subscription` (i.e., one `Source`) can only used for one MV (shared between the actors of its fragment). Otherwise, different MVs on the same Source will both receive part of the messages. TODO: check and enforce this on Meta.' required: true - name: pubsub.emulator_host field_type: String @@ -585,26 +664,35 @@ PubsubProperties: required: false - name: pubsub.start_snapshot field_type: String - comments: '`start_snapshot` is a named pub/sub snapshot. If present, the connector will first seek to the snapshot before starting consumption. Snapshots are the preferred seeking mechanism in pub/sub because they guarantee retention of: - All unacknowledged messages at the time of their creation. - All messages created after their creation. Besides retention guarantees, timestamps are also more precise than timestamp-based seeks. See [Seeking to a snapshot](https://cloud.google.com/pubsub/docs/replay-overview#seeking_to_a_timestamp) for more details.' + comments: '`start_snapshot` is a named pub/sub snapshot. If present, the connector will first seek to the snapshot before starting consumption. Snapshots are the preferred seeking mechanism in pub/sub because they guarantee retention of: - All unacknowledged messages at the time of their creation. - All messages created after their creation. Besides retention guarantees, snapshots are also more precise than timestamp-based seeks. See [Seeking to a snapshot](https://cloud.google.com/pubsub/docs/replay-overview#seeking_to_a_timestamp) for more details.' + required: false + - name: pubsub.parallelism + field_type: u32 + comments: '`parallelism` is the number of parallel consumers to run for the subscription. TODO: use system parallelism if not set' required: false PulsarProperties: fields: - name: scan.startup.mode field_type: String required: false - alias: pulsar.scan.startup.mode + alias: + - pulsar.scan.startup.mode - name: scan.startup.timestamp.millis field_type: String required: false - alias: scan.startup.timestamp_millis + alias: + - pulsar.time.offset + - scan.startup.timestamp_millis - name: topic field_type: String required: true - alias: pulsar.topic + alias: + - pulsar.topic - name: service.url field_type: String required: true - alias: pulsar.service.url + alias: + - pulsar.service.url - name: auth.token field_type: String required: false @@ -620,32 +708,49 @@ PulsarProperties: - name: oauth.scope field_type: String required: false - - name: region + - name: aws.region field_type: String required: false - - name: endpoint + alias: + - region + - name: aws.endpoint_url field_type: String required: false - alias: endpoint_url - - name: access_key + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id field_type: String required: false - - name: secret_key + alias: + - access_key + - name: aws.credentials.secret_access_key field_type: String required: false - - name: session_token + alias: + - secret_key + - name: aws.credentials.session_token field_type: String required: false - - name: arn + alias: + - session_token + - name: aws.credentials.role.arn field_type: String + comments: IAM role required: false - - name: external_id + alias: + - arn + - name: aws.credentials.role.external_id field_type: String - comments: This field was added for kinesis. Not sure if it's useful for other connectors. Please ignore it in the documentation for now. + comments: external ID in IAM role trust policy required: false - - name: profile + alias: + - external_id + - name: aws.profile field_type: String required: false + alias: + - profile - name: iceberg.enabled field_type: bool required: false @@ -676,8 +781,12 @@ S3Properties: - name: s3.endpoint_url field_type: String required: false + - name: compression_format + field_type: CompressionFormat + required: false + default: Default::default TestSourceProperties: fields: - name: properties - field_type: HashMap - required: false + field_type: BTreeMap + required: true diff --git a/src/ctl/Cargo.toml b/src/ctl/Cargo.toml index 109d412ba1e89..4709090d2407d 100644 --- a/src/ctl/Cargo.toml +++ b/src/ctl/Cargo.toml @@ -20,6 +20,7 @@ chrono = "0.4" clap = { workspace = true } comfy-table = "7" etcd-client = { workspace = true } +foyer ={ workspace = true } futures = { version = "0.3", default-features = false, features = ["alloc"] } hex = "0.4" inquire = "0.7.0" @@ -54,6 +55,7 @@ tokio = { version = "0.2", package = "madsim-tokio", features = [ "signal", "fs", ] } +tonic = { workspace = true } tracing = "0.1" uuid = { version = "1", features = ["v4"] } diff --git a/src/ctl/src/cmd_impl/await_tree.rs b/src/ctl/src/cmd_impl/await_tree.rs index 09c7c36119262..1c4ff98562791 100644 --- a/src/ctl/src/cmd_impl/await_tree.rs +++ b/src/ctl/src/cmd_impl/await_tree.rs @@ -13,21 +13,15 @@ // limitations under the License. use risingwave_common::util::addr::HostAddr; +use risingwave_common::util::StackTraceResponseExt; use risingwave_pb::common::WorkerType; use risingwave_pb::monitor_service::StackTraceResponse; use risingwave_rpc_client::{CompactorClient, ComputeClientPool}; use crate::CtlContext; -fn merge(a: &mut StackTraceResponse, b: StackTraceResponse) { - a.actor_traces.extend(b.actor_traces); - a.rpc_traces.extend(b.rpc_traces); - a.compaction_task_traces.extend(b.compaction_task_traces); - a.inflight_barrier_traces.extend(b.inflight_barrier_traces); -} - pub async fn dump(context: &CtlContext) -> anyhow::Result<()> { - let mut all = Default::default(); + let mut all = StackTraceResponse::default(); let meta_client = context.meta_client().await?; @@ -41,7 +35,7 @@ pub async fn dump(context: &CtlContext) -> anyhow::Result<()> { for cn in compute_nodes { let client = clients.get(&cn).await?; let response = client.stack_trace().await?; - merge(&mut all, response); + all.merge_other(response); } let compactor_nodes = meta_client @@ -52,7 +46,7 @@ pub async fn dump(context: &CtlContext) -> anyhow::Result<()> { let addr: HostAddr = compactor.get_host().unwrap().into(); let client = CompactorClient::new(addr).await?; let response = client.stack_trace().await?; - merge(&mut all, response); + all.merge_other(response); } if all.actor_traces.is_empty() @@ -61,32 +55,8 @@ pub async fn dump(context: &CtlContext) -> anyhow::Result<()> { && all.inflight_barrier_traces.is_empty() { println!("No traces found. No actors are running, or `--async-stack-trace` not set?"); - } else { - if !all.actor_traces.is_empty() { - println!("--- Actor Traces ---"); - for (key, trace) in all.actor_traces { - println!(">> Actor {key}\n{trace}"); - } - } - if !all.rpc_traces.is_empty() { - println!("\n\n--- RPC Traces ---"); - for (key, trace) in all.rpc_traces { - println!(">> RPC {key}\n{trace}"); - } - } - if !all.compaction_task_traces.is_empty() { - println!("\n\n--- Compactor Traces ---"); - for (key, trace) in all.compaction_task_traces { - println!(">> Compaction Task {key}\n{trace}"); - } - } - if !all.inflight_barrier_traces.is_empty() { - println!("\n\n--- Inflight Barrier Traces ---"); - for (name, trace) in &all.inflight_barrier_traces { - println!(">> Barrier {name}\n{trace}"); - } - } } + println!("{}", all.output()); Ok(()) } diff --git a/src/ctl/src/cmd_impl/bench.rs b/src/ctl/src/cmd_impl/bench.rs index d3c0cde6d20ee..dce4a21115d6a 100644 --- a/src/ctl/src/cmd_impl/bench.rs +++ b/src/ctl/src/cmd_impl/bench.rs @@ -42,6 +42,8 @@ pub enum BenchCommands { #[clap(long, default_value_t = 1)] threads: usize, data_dir: Option, + #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")] + use_new_object_prefix_strategy: bool, }, } @@ -86,9 +88,13 @@ pub async fn do_bench(context: &CtlContext, cmd: BenchCommands) -> Result<()> { mv_name, threads, data_dir, + use_new_object_prefix_strategy, } => { let (hummock, metrics) = context - .hummock_store_with_metrics(HummockServiceOpts::from_env(data_dir)?) + .hummock_store_with_metrics(HummockServiceOpts::from_env( + data_dir, + use_new_object_prefix_strategy, + )?) .await?; let table = get_table_catalog(meta.clone(), mv_name).await?; let mut handlers = vec![]; diff --git a/src/ctl/src/cmd_impl/compute.rs b/src/ctl/src/cmd_impl/compute.rs index 0150c00058773..ff519efcfffa7 100644 --- a/src/ctl/src/cmd_impl/compute.rs +++ b/src/ctl/src/cmd_impl/compute.rs @@ -11,12 +11,10 @@ // 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::convert::TryFrom; use risingwave_common::config::{BatchConfig, StreamingConfig}; use risingwave_common::util::addr::HostAddr; use risingwave_rpc_client::ComputeClient; -use serde_json; pub async fn show_config(host: &str) -> anyhow::Result<()> { let listen_addr = HostAddr::try_from(host)?; diff --git a/src/ctl/src/cmd_impl/debug/fix_table_fragments.rs b/src/ctl/src/cmd_impl/debug/fix_table_fragments.rs index 627545ac178e2..e164fde3e7845 100644 --- a/src/ctl/src/cmd_impl/debug/fix_table_fragments.rs +++ b/src/ctl/src/cmd_impl/debug/fix_table_fragments.rs @@ -13,7 +13,7 @@ // limitations under the License. use etcd_client::ConnectOptions; -use risingwave_common::util::stream_graph_visitor::visit_stream_node_cont; +use risingwave_common::util::stream_graph_visitor::visit_stream_node_cont_mut; use risingwave_meta::model::{MetadataModel, TableFragments}; use risingwave_meta::storage::{EtcdMetaStore, WrappedEtcdClient}; use risingwave_pb::stream_plan::stream_node::NodeBody; @@ -54,7 +54,7 @@ pub async fn fix_table_fragments( .upstream_fragment_ids .retain(|id| !dirty_fragment_ids.contains(id)); for actor in &mut fragment.actors { - visit_stream_node_cont(actor.nodes.as_mut().unwrap(), |node| { + visit_stream_node_cont_mut(actor.nodes.as_mut().unwrap(), |node| { if let Some(NodeBody::Union(_)) = node.node_body { node.input.retain_mut(|input| { if let Some(NodeBody::Merge(merge_node)) = &mut input.node_body diff --git a/src/ctl/src/cmd_impl/hummock.rs b/src/ctl/src/cmd_impl/hummock.rs index 36e3d5539b849..2f76c11a1bc0d 100644 --- a/src/ctl/src/cmd_impl/hummock.rs +++ b/src/ctl/src/cmd_impl/hummock.rs @@ -21,6 +21,7 @@ pub use sst_dump::*; mod compaction_group; mod list_version_deltas; mod pause_resume; +mod tiered_cache_tracing; mod trigger_full_gc; mod trigger_manual_compaction; mod validate_version; @@ -28,6 +29,7 @@ mod validate_version; pub use compaction_group::*; pub use list_version_deltas::*; pub use pause_resume::*; +pub use tiered_cache_tracing::*; pub use trigger_full_gc::*; pub use trigger_manual_compaction::*; pub use validate_version::*; diff --git a/src/ctl/src/cmd_impl/hummock/compaction_group.rs b/src/ctl/src/cmd_impl/hummock/compaction_group.rs index 068472dd1ce4a..d58aeb7bffe79 100644 --- a/src/ctl/src/cmd_impl/hummock/compaction_group.rs +++ b/src/ctl/src/cmd_impl/hummock/compaction_group.rs @@ -20,6 +20,7 @@ use risingwave_hummock_sdk::compaction_group::StateTableId; use risingwave_hummock_sdk::{CompactionGroupId, HummockContextId}; use risingwave_pb::hummock::compact_task::TaskStatus; use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::mutable_config::MutableConfig; +use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::CompressionAlgorithm; use crate::CtlContext; @@ -63,6 +64,8 @@ pub fn build_compaction_config_vec( level0_overlapping_sub_level_compact_level_count: Option, enable_emergency_picker: Option, tombstone_reclaim_ratio: Option, + compress_algorithm: Option, + max_l0_compact_level: Option, ) -> Vec { let mut configs = vec![]; if let Some(c) = max_bytes_for_level_base { @@ -110,6 +113,12 @@ pub fn build_compaction_config_vec( if let Some(c) = tombstone_reclaim_ratio { configs.push(MutableConfig::TombstoneReclaimRatio(c)) } + if let Some(c) = compress_algorithm { + configs.push(MutableConfig::CompressionAlgorithm(c)) + } + if let Some(c) = max_l0_compact_level { + configs.push(MutableConfig::MaxL0CompactLevelCount(c)) + } configs } diff --git a/src/ctl/src/cmd_impl/hummock/list_kv.rs b/src/ctl/src/cmd_impl/hummock/list_kv.rs index 2eb54362b413c..f90712a02505d 100644 --- a/src/ctl/src/cmd_impl/hummock/list_kv.rs +++ b/src/ctl/src/cmd_impl/hummock/list_kv.rs @@ -27,9 +27,13 @@ pub async fn list_kv( epoch: u64, table_id: u32, data_dir: Option, + use_new_object_prefix_strategy: bool, ) -> anyhow::Result<()> { let hummock = context - .hummock_store(HummockServiceOpts::from_env(data_dir)?) + .hummock_store(HummockServiceOpts::from_env( + data_dir, + use_new_object_prefix_strategy, + )?) .await?; if is_max_epoch(epoch) { tracing::info!("using MAX EPOCH as epoch"); diff --git a/src/ctl/src/cmd_impl/hummock/pause_resume.rs b/src/ctl/src/cmd_impl/hummock/pause_resume.rs index e8574ce60af7e..85791267bfd8a 100644 --- a/src/ctl/src/cmd_impl/hummock/pause_resume.rs +++ b/src/ctl/src/cmd_impl/hummock/pause_resume.rs @@ -61,7 +61,7 @@ pub async fn replay_version(context: &CtlContext) -> anyhow::Result<()> { println!("replay starts"); println!("base version {}", base_version.id); let delta_fetch_size = 100; - let mut current_delta_id = base_version.id + 1; + let mut current_delta_id = base_version.next_version_id(); loop { let deltas = meta_client .list_version_deltas(current_delta_id, delta_fetch_size, HummockEpoch::MAX) @@ -78,7 +78,7 @@ pub async fn replay_version(context: &CtlContext) -> anyhow::Result<()> { base_version.apply_version_delta(&delta); println!("replayed version {}", base_version.id); } - current_delta_id = base_version.id + 1; + current_delta_id = base_version.next_version_id(); } println!("replay ends"); Ok(()) diff --git a/src/ctl/src/cmd_impl/hummock/sst_dump.rs b/src/ctl/src/cmd_impl/hummock/sst_dump.rs index 0fc65054b51e4..3a71fbd007214 100644 --- a/src/ctl/src/cmd_impl/hummock/sst_dump.rs +++ b/src/ctl/src/cmd_impl/hummock/sst_dump.rs @@ -15,7 +15,6 @@ use std::collections::HashMap; use std::sync::Arc; -use anyhow::anyhow; use bytes::{Buf, Bytes}; use chrono::offset::Utc; use chrono::DateTime; @@ -59,6 +58,8 @@ pub struct SstDumpArgs { print_table: bool, #[clap(short = 'd')] data_dir: Option, + #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")] + use_new_object_prefix_strategy: bool, } pub async fn sst_dump(context: &CtlContext, args: SstDumpArgs) -> anyhow::Result<()> { @@ -72,7 +73,10 @@ pub async fn sst_dump(context: &CtlContext, args: SstDumpArgs) -> anyhow::Result if args.print_level { // Level information is retrieved from meta service let hummock = context - .hummock_store(HummockServiceOpts::from_env(args.data_dir.clone())?) + .hummock_store(HummockServiceOpts::from_env( + args.data_dir.clone(), + args.use_new_object_prefix_strategy, + )?) .await?; let version = hummock.inner().get_pinned_version().version().clone(); let sstable_store = hummock.sstable_store(); @@ -108,16 +112,17 @@ pub async fn sst_dump(context: &CtlContext, args: SstDumpArgs) -> anyhow::Result } } else { // Object information is retrieved from object store. Meta service is not required. - let hummock_service_opts = HummockServiceOpts::from_env(args.data_dir.clone())?; - let sstable_store = hummock_service_opts.create_sstable_store().await?; + let hummock_service_opts = HummockServiceOpts::from_env( + args.data_dir.clone(), + args.use_new_object_prefix_strategy, + )?; + let sstable_store = hummock_service_opts + .create_sstable_store(args.use_new_object_prefix_strategy) + .await?; if let Some(obj_id) = &args.object_id { let obj_store = sstable_store.store(); let obj_path = sstable_store.get_sst_data_path(*obj_id); - let mut obj_metadata_iter = obj_store.list(&obj_path).await?; - let obj = obj_metadata_iter - .try_next() - .await? - .ok_or_else(|| anyhow!(format!("object {obj_path} doesn't exist")))?; + let obj = obj_store.metadata(&obj_path).await?; print_object(&obj); let meta_offset = get_meta_offset_from_object(&obj, obj_store.as_ref()).await?; let obj_id = SstableStore::get_object_id_from_path(&obj.key); diff --git a/src/ctl/src/cmd_impl/hummock/tiered_cache_tracing.rs b/src/ctl/src/cmd_impl/hummock/tiered_cache_tracing.rs new file mode 100644 index 0000000000000..a2eee44fa4295 --- /dev/null +++ b/src/ctl/src/cmd_impl/hummock/tiered_cache_tracing.rs @@ -0,0 +1,61 @@ +// Copyright 2024 RisingWave Labs +// +// 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::Duration; + +use futures::future::try_join_all; +use itertools::Itertools; +use risingwave_pb::monitor_service::monitor_service_client::MonitorServiceClient; +use risingwave_pb::monitor_service::TieredCacheTracingRequest; +use tonic::transport::Endpoint; + +use crate::common::CtlContext; + +pub async fn tiered_cache_tracing( + context: &CtlContext, + enable: bool, + record_hybrid_insert_threshold_ms: Option, + record_hybrid_get_threshold_ms: Option, + record_hybrid_obtain_threshold_ms: Option, + record_hybrid_remove_threshold_ms: Option, + record_hybrid_fetch_threshold_ms: Option, +) -> anyhow::Result<()> { + let meta_client = context.meta_client().await?; + let info = meta_client.get_cluster_info().await?; + let futures = info + .get_worker_nodes() + .iter() + .map(|worker_node| async { + let addr = worker_node.get_host().unwrap(); + let channel = Endpoint::from_shared(format!("http://{}:{}", addr.host, addr.port))? + .connect_timeout(Duration::from_secs(5)) + .connect() + .await?; + let mut client = MonitorServiceClient::new(channel); + client + .tiered_cache_tracing(TieredCacheTracingRequest { + enable, + record_hybrid_insert_threshold_ms, + record_hybrid_get_threshold_ms, + record_hybrid_obtain_threshold_ms, + record_hybrid_remove_threshold_ms, + record_hybrid_fetch_threshold_ms, + }) + .await?; + Ok::<_, anyhow::Error>(()) + }) + .collect_vec(); + try_join_all(futures).await?; + Ok(()) +} diff --git a/src/ctl/src/cmd_impl/hummock/validate_version.rs b/src/ctl/src/cmd_impl/hummock/validate_version.rs index b2ae1c22f66cf..e8f61a8c98358 100644 --- a/src/ctl/src/cmd_impl/hummock/validate_version.rs +++ b/src/ctl/src/cmd_impl/hummock/validate_version.rs @@ -65,6 +65,7 @@ pub async fn print_user_key_in_archive( archive_ids: Vec, data_dir: String, user_key: String, + use_new_object_prefix_strategy: bool, ) -> anyhow::Result<()> { let user_key_bytes = hex::decode(user_key.clone()).unwrap_or_else(|_| { panic!("cannot decode user key {} into raw bytes", user_key); @@ -72,7 +73,8 @@ pub async fn print_user_key_in_archive( let user_key = UserKey::decode(&user_key_bytes); println!("user key: {user_key:?}"); - let hummock_opts = HummockServiceOpts::from_env(Some(data_dir.clone()))?; + let hummock_opts = + HummockServiceOpts::from_env(Some(data_dir.clone()), use_new_object_prefix_strategy)?; let hummock = context.hummock_store(hummock_opts).await?; let sstable_store = hummock.sstable_store(); let archive_object_store = sstable_store.store(); @@ -178,8 +180,10 @@ pub async fn print_version_delta_in_archive( archive_ids: Vec, data_dir: String, sst_id: HummockSstableObjectId, + use_new_object_prefix_strategy: bool, ) -> anyhow::Result<()> { - let hummock_opts = HummockServiceOpts::from_env(Some(data_dir.clone()))?; + let hummock_opts = + HummockServiceOpts::from_env(Some(data_dir.clone()), use_new_object_prefix_strategy)?; let hummock = context.hummock_store(hummock_opts).await?; let sstable_store = hummock.sstable_store(); let archive_object_store = sstable_store.store(); diff --git a/src/ctl/src/cmd_impl/meta/migration.rs b/src/ctl/src/cmd_impl/meta/migration.rs index 8a77ca5650f9d..614fcd6be225c 100644 --- a/src/ctl/src/cmd_impl/meta/migration.rs +++ b/src/ctl/src/cmd_impl/meta/migration.rs @@ -12,10 +12,11 @@ // 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::time::Duration; use anyhow::Context; +use chrono::DateTime; use etcd_client::ConnectOptions; use itertools::Itertools; use risingwave_common::util::epoch::Epoch; @@ -45,20 +46,20 @@ use risingwave_meta_model_v2::hummock_version_stats::TableStats; use risingwave_meta_model_v2::object::ObjectType; use risingwave_meta_model_v2::prelude::{ Actor, ActorDispatcher, CatalogVersion, Cluster, Connection, Database, Fragment, Function, - Index, Object, ObjectDependency, Schema, Sink, Source, StreamingJob, Subscription, + Index, Object, ObjectDependency, Schema, Secret, Sink, Source, StreamingJob, Subscription, SystemParameter, Table, User, UserPrivilege, View, Worker, WorkerProperty, }; use risingwave_meta_model_v2::{ catalog_version, cluster, compaction_config, compaction_status, compaction_task, connection, database, function, hummock_pinned_snapshot, hummock_pinned_version, hummock_sequence, - hummock_version_delta, hummock_version_stats, index, object, object_dependency, schema, sink, - source, streaming_job, subscription, table, user, user_privilege, view, worker, + hummock_version_delta, hummock_version_stats, index, object, object_dependency, schema, secret, + sink, source, streaming_job, subscription, table, user, user_privilege, view, worker, worker_property, CreateType, JobStatus, ObjectId, StreamingParallelism, }; use risingwave_pb::catalog::table::PbTableType; use risingwave_pb::catalog::{ - PbConnection, PbDatabase, PbFunction, PbIndex, PbSchema, PbSink, PbSource, PbSubscription, - PbTable, PbView, + PbConnection, PbDatabase, PbFunction, PbIndex, PbSchema, PbSecret, PbSink, PbSource, + PbSubscription, PbTable, PbView, }; use risingwave_pb::common::WorkerType; use risingwave_pb::hummock::{ @@ -68,7 +69,6 @@ use risingwave_pb::meta::table_fragments::State; use risingwave_pb::meta::PbSystemParams; use risingwave_pb::user::grant_privilege::PbObject as GrantObject; use risingwave_pb::user::PbUserInfo; -use sea_orm::prelude::DateTime; use sea_orm::ActiveValue::Set; use sea_orm::{ ColumnTrait, ConnectionTrait, DatabaseBackend, DbBackend, EntityTrait, IntoActiveModel, NotSet, @@ -188,6 +188,7 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an let functions = PbFunction::list(&meta_store).await?; let connections = PbConnection::list(&meta_store).await?; let subscriptions = PbSubscription::list(&meta_store).await?; + let secrets = PbSecret::list(&meta_store).await?; // inuse object ids. let mut inuse_obj_ids = tables @@ -319,6 +320,28 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an } println!("connections migrated"); + // secret mapping + let mut secret_rewrite = HashMap::new(); + for mut secret in secrets { + let id = next_available_id(); + secret_rewrite.insert(secret.id, id); + secret.id = id as _; + + let obj = object::ActiveModel { + oid: Set(id as _), + obj_type: Set(ObjectType::Secret), + owner_id: Set(secret.owner as _), + database_id: Set(Some(*db_rewrite.get(&secret.database_id).unwrap() as _)), + schema_id: Set(Some(*schema_rewrite.get(&secret.schema_id).unwrap() as _)), + ..Default::default() + }; + Object::insert(obj).exec(&meta_store_sql.conn).await?; + Secret::insert(secret::ActiveModel::from(secret)) + .exec(&meta_store_sql.conn) + .await?; + } + println!("secrets migrated"); + // add object: table, source, sink, index, view, subscription. macro_rules! insert_objects { ($objects:expr, $object_type:expr) => { @@ -337,11 +360,15 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an }; if let Some(epoch) = object.initialized_at_epoch.map(Epoch::from) { obj.initialized_at = - Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _).unwrap()); + Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _) + .unwrap() + .naive_utc()); } if let Some(epoch) = object.created_at_epoch.map(Epoch::from) { obj.created_at = - Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _).unwrap()); + Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _) + .unwrap() + .naive_utc()); } Object::insert(obj).exec(&meta_store_sql.conn).await?; } @@ -367,12 +394,14 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an ..Default::default() }; if let Some(epoch) = table.initialized_at_epoch.map(Epoch::from) { - obj.initialized_at = - Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _).unwrap()); + obj.initialized_at = Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _) + .unwrap() + .naive_utc()); } if let Some(epoch) = table.created_at_epoch.map(Epoch::from) { - obj.created_at = - Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _).unwrap()); + obj.created_at = Set(DateTime::from_timestamp_millis(epoch.as_unix_millis() as _) + .unwrap() + .naive_utc()); } Object::insert(obj).exec(&meta_store_sql.conn).await?; } @@ -460,15 +489,35 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an } println!("table fragments migrated"); + let mut object_dependencies = vec![]; + // catalogs. // source if !sources.is_empty() { let source_models: Vec = sources .into_iter() .map(|mut src| { + let mut dependent_secret_ids = HashSet::new(); if let Some(id) = src.connection_id.as_mut() { *id = *connection_rewrite.get(id).unwrap(); } + for secret_ref in src.secret_refs.values_mut() { + secret_ref.secret_id = *secret_rewrite.get(&secret_ref.secret_id).unwrap(); + dependent_secret_ids.insert(secret_ref.secret_id); + } + if let Some(info) = &mut src.info { + for secret_ref in info.format_encode_secret_refs.values_mut() { + secret_ref.secret_id = *secret_rewrite.get(&secret_ref.secret_id).unwrap(); + dependent_secret_ids.insert(secret_ref.secret_id); + } + } + object_dependencies.extend(dependent_secret_ids.into_iter().map(|secret_id| { + object_dependency::ActiveModel { + id: NotSet, + oid: Set(secret_id as _), + used_by: Set(src.id as _), + } + })); src.into() }) .collect(); @@ -478,8 +527,6 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an } println!("sources migrated"); - let mut object_dependencies = vec![]; - // table for table in tables { let job_id = if table.table_type() == PbTableType::Internal { @@ -525,6 +572,24 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an if let Some(id) = s.connection_id.as_mut() { *id = *connection_rewrite.get(id).unwrap(); } + let mut dependent_secret_ids = HashSet::new(); + for secret_ref in s.secret_refs.values_mut() { + secret_ref.secret_id = *secret_rewrite.get(&secret_ref.secret_id).unwrap(); + dependent_secret_ids.insert(secret_ref.secret_id); + } + if let Some(desc) = &mut s.format_desc { + for secret_ref in desc.secret_refs.values_mut() { + secret_ref.secret_id = *secret_rewrite.get(&secret_ref.secret_id).unwrap(); + dependent_secret_ids.insert(secret_ref.secret_id); + } + } + object_dependencies.extend(dependent_secret_ids.into_iter().map(|secret_id| { + object_dependency::ActiveModel { + id: NotSet, + oid: Set(secret_id as _), + used_by: Set(s.id as _), + } + })); s.into() }) .collect(); @@ -560,13 +625,11 @@ pub async fn migrate(from: EtcdBackend, target: String, force_clean: bool) -> an let subscription_models: Vec = subscriptions .into_iter() .map(|s| { - object_dependencies.extend(s.dependent_relations.iter().map(|id| { - object_dependency::ActiveModel { - id: NotSet, - oid: Set(*id as _), - used_by: Set(s.id as _), - } - })); + object_dependencies.push(object_dependency::ActiveModel { + id: NotSet, + oid: Set(s.dependent_table_id as _), + used_by: Set(s.id as _), + }); s.into() }) .collect(); diff --git a/src/ctl/src/cmd_impl/meta/reschedule.rs b/src/ctl/src/cmd_impl/meta/reschedule.rs index 64d3e229c9112..982b79e108b6c 100644 --- a/src/ctl/src/cmd_impl/meta/reschedule.rs +++ b/src/ctl/src/cmd_impl/meta/reschedule.rs @@ -20,11 +20,9 @@ use inquire::Confirm; use itertools::Itertools; use regex::{Match, Regex}; use risingwave_pb::common::WorkerNode; -use risingwave_pb::meta::get_reschedule_plan_request::PbPolicy; use risingwave_pb::meta::table_fragments::ActorStatus; -use risingwave_pb::meta::{GetClusterInfoResponse, GetReschedulePlanResponse, Reschedule}; +use risingwave_pb::meta::{GetClusterInfoResponse, Reschedule}; use serde::{Deserialize, Serialize}; -use serde_yaml; use thiserror_ext::AsReport; use crate::CtlContext; @@ -226,16 +224,6 @@ fn parse_plan(mut plan: String) -> Result, Error> { Ok(reschedules) } -pub async fn get_reschedule_plan( - context: &CtlContext, - policy: PbPolicy, - revision: u64, -) -> Result { - let meta_client = context.meta_client().await?; - let response = meta_client.get_reschedule_plan(policy, revision).await?; - Ok(response) -} - pub async fn unregister_workers( context: &CtlContext, workers: Vec, diff --git a/src/ctl/src/cmd_impl/meta/serving.rs b/src/ctl/src/cmd_impl/meta/serving.rs index c6c5d3cf81985..cd8b1be50e206 100644 --- a/src/ctl/src/cmd_impl/meta/serving.rs +++ b/src/ctl/src/cmd_impl/meta/serving.rs @@ -16,30 +16,26 @@ use std::collections::HashMap; use comfy_table::{Row, Table}; use itertools::Itertools; -use risingwave_common::hash::{ParallelUnitId, VirtualNode}; -use risingwave_pb::common::{WorkerNode, WorkerType}; +use risingwave_common::hash::VirtualNode; +use risingwave_pb::common::WorkerType; use crate::CtlContext; pub async fn list_serving_fragment_mappings(context: &CtlContext) -> anyhow::Result<()> { let meta_client = context.meta_client().await?; let mappings = meta_client.list_serving_vnode_mappings().await?; - let workers = meta_client + let workers: HashMap<_, _> = meta_client .list_worker_nodes(Some(WorkerType::ComputeNode)) - .await?; - let mut pu_to_worker: HashMap = HashMap::new(); - for w in &workers { - for pu in &w.parallel_units { - pu_to_worker.insert(pu.id, w); - } - } + .await? + .into_iter() + .map(|worker| (worker.id, worker)) + .collect(); let mut table = Table::new(); table.set_header({ let mut row = Row::new(); row.add_cell("Table Id".into()); row.add_cell("Fragment Id".into()); - row.add_cell("Parallel Unit Id".into()); row.add_cell("Virtual Node".into()); row.add_cell("Worker".into()); row @@ -48,28 +44,25 @@ pub async fn list_serving_fragment_mappings(context: &CtlContext) -> anyhow::Res let rows = mappings .iter() .flat_map(|(fragment_id, (table_id, mapping))| { - let mut pu_vnodes: HashMap> = HashMap::new(); - for (vnode, pu) in mapping.iter_with_vnode() { - pu_vnodes.entry(pu).or_default().push(vnode); + let mut worker_nodes: HashMap> = HashMap::new(); + for (vnode, worker_slot_id) in mapping.iter_with_vnode() { + worker_nodes + .entry(worker_slot_id.worker_id()) + .or_default() + .push(vnode); } - pu_vnodes.into_iter().map(|(pu_id, vnodes)| { - ( - *table_id, - *fragment_id, - pu_id, - vnodes, - pu_to_worker.get(&pu_id), - ) + worker_nodes.into_iter().map(|(worker_id, vnodes)| { + (*table_id, *fragment_id, vnodes, workers.get(&worker_id)) }) }) .collect_vec(); - for (table_id, fragment_id, pu_id, vnodes, worker) in - rows.into_iter().sorted_by_key(|(t, f, p, ..)| (*t, *f, *p)) + + for (table_id, fragment_id, vnodes, worker) in + rows.into_iter().sorted_by_key(|(t, f, ..)| (*t, *f)) { let mut row = Row::new(); row.add_cell(table_id.into()); row.add_cell(fragment_id.into()); - row.add_cell(pu_id.into()); row.add_cell( format!( "{} in total: {}", diff --git a/src/ctl/src/cmd_impl/scale/resize.rs b/src/ctl/src/cmd_impl/scale/resize.rs index 6ef0b3c15d7e1..364426e7996eb 100644 --- a/src/ctl/src/cmd_impl/scale/resize.rs +++ b/src/ctl/src/cmd_impl/scale/resize.rs @@ -13,23 +13,14 @@ // limitations under the License. use std::collections::{HashMap, HashSet}; -use std::ops::Sub; use std::process::exit; -use inquire::Confirm; use itertools::Itertools; -use risingwave_pb::meta::get_reschedule_plan_request::{ - PbPolicy, StableResizePolicy, WorkerChanges, -}; use risingwave_pb::meta::update_worker_node_schedulability_request::Schedulability; -use risingwave_pb::meta::{GetClusterInfoResponse, GetReschedulePlanResponse}; -use risingwave_stream::task::FragmentId; -use serde_yaml; +use risingwave_pb::meta::GetClusterInfoResponse; use thiserror_ext::AsReport; -use crate::cmd_impl::meta::ReschedulePayload; use crate::common::CtlContext; -use crate::{ScaleCommon, ScaleHorizonCommands, ScaleVerticalCommands}; macro_rules! fail { ($($arg:tt)*) => {{ @@ -38,350 +29,6 @@ macro_rules! fail { }}; } -impl From for ScaleCommandContext { - fn from(value: ScaleHorizonCommands) -> Self { - let ScaleHorizonCommands { - exclude_workers, - include_workers, - target_parallelism, - common: - ScaleCommon { - generate, - output, - yes, - fragments, - }, - } = value; - - Self { - exclude_workers, - include_workers, - target_parallelism, - generate, - output, - yes, - fragments, - target_parallelism_per_worker: None, - exclusive_for_vertical: false, - } - } -} - -impl From for ScaleCommandContext { - fn from(value: ScaleVerticalCommands) -> Self { - let ScaleVerticalCommands { - workers, - target_parallelism_per_worker, - common: - ScaleCommon { - generate, - output, - yes, - fragments, - }, - exclusive, - } = value; - - Self { - exclude_workers: None, - include_workers: workers, - target_parallelism: None, - generate, - output, - yes, - fragments, - target_parallelism_per_worker, - exclusive_for_vertical: exclusive, - } - } -} - -pub struct ScaleCommandContext { - exclude_workers: Option>, - include_workers: Option>, - target_parallelism: Option, - generate: bool, - output: Option, - yes: bool, - fragments: Option>, - target_parallelism_per_worker: Option, - exclusive_for_vertical: bool, -} - -pub async fn resize(ctl_ctx: &CtlContext, scale_ctx: ScaleCommandContext) -> anyhow::Result<()> { - let meta_client = ctl_ctx.meta_client().await?; - - let GetClusterInfoResponse { - worker_nodes, - table_fragments, - actor_splits: _actor_splits, - source_infos: _source_infos, - revision, - } = match meta_client.get_cluster_info().await { - Ok(resp) => resp, - Err(e) => { - fail!("Failed to fetch cluster info: {}", e.as_report()); - } - }; - - if worker_nodes.is_empty() { - println!("No worker nodes found"); - return Ok(()); - } - - if table_fragments.is_empty() { - println!("No tables found"); - return Ok(()); - } - - println!("Cluster info fetched, revision: {}", revision); - println!("Worker nodes: {}", worker_nodes.len()); - - let streaming_workers_index_by_id = worker_nodes - .into_iter() - .filter(|worker| { - worker - .property - .as_ref() - .map(|property| property.is_streaming) - .unwrap_or(false) - }) - .map(|worker| (worker.id, worker)) - .collect::>(); - - let streaming_workers_index_by_host = streaming_workers_index_by_id - .values() - .map(|worker| { - let host = worker.get_host().expect("worker host must be set"); - (format!("{}:{}", host.host, host.port), worker.clone()) - }) - .collect::>(); - - let worker_input_to_worker_ids = |inputs: Vec, support_all: bool| -> Vec { - let mut result: HashSet<_> = HashSet::new(); - - if inputs.len() == 1 && inputs[0].to_lowercase() == "all" && support_all { - return streaming_workers_index_by_id.keys().cloned().collect(); - } - - for input in inputs { - let worker_id = input.parse::().ok().or_else(|| { - streaming_workers_index_by_host - .get(&input) - .map(|worker| worker.id) - }); - - if let Some(worker_id) = worker_id { - if !result.insert(worker_id) { - println!("warn: {} and {} are the same worker", input, worker_id); - } - } else { - fail!("Invalid worker input: {}", input); - } - } - - result.into_iter().collect() - }; - - println!( - "Streaming workers found: {}", - streaming_workers_index_by_id.len() - ); - - let ScaleCommandContext { - exclude_workers, - include_workers, - target_parallelism, - target_parallelism_per_worker, - generate, - output, - yes, - fragments, - exclusive_for_vertical, - } = scale_ctx; - - let worker_changes = { - let mut exclude_worker_ids = - worker_input_to_worker_ids(exclude_workers.unwrap_or_default(), false); - let include_worker_ids = - worker_input_to_worker_ids(include_workers.unwrap_or_default(), true); - - match (target_parallelism, target_parallelism_per_worker) { - (Some(_), Some(_)) => { - fail!("Cannot specify both target parallelism and target parallelism per worker") - } - (_, Some(_)) if include_worker_ids.is_empty() => { - fail!("Cannot specify target parallelism per worker without including any worker") - } - (Some(0), _) => fail!("Target parallelism must be greater than 0"), - _ => {} - } - - for worker_id in exclude_worker_ids.iter().chain(include_worker_ids.iter()) { - if !streaming_workers_index_by_id.contains_key(worker_id) { - fail!("Invalid worker id: {}", worker_id); - } - } - - for include_worker_id in &include_worker_ids { - let worker_is_unschedulable = streaming_workers_index_by_id - .get(include_worker_id) - .and_then(|worker| worker.property.as_ref()) - .map(|property| property.is_unschedulable) - .unwrap_or(false); - - if worker_is_unschedulable { - fail!( - "Worker {} is unschedulable, should not be included", - include_worker_id - ); - } - } - - if exclusive_for_vertical { - let all_worker_ids: HashSet<_> = - streaming_workers_index_by_id.keys().cloned().collect(); - - let include_worker_id_set: HashSet<_> = include_worker_ids.iter().cloned().collect(); - let generated_exclude_worker_ids = all_worker_ids.sub(&include_worker_id_set); - - exclude_worker_ids = exclude_worker_ids - .into_iter() - .chain(generated_exclude_worker_ids) - .unique() - .collect(); - } - - WorkerChanges { - include_worker_ids, - exclude_worker_ids, - target_parallelism, - target_parallelism_per_worker, - } - }; - - let all_fragment_ids: HashSet<_> = table_fragments - .iter() - .flat_map(|table_fragments| table_fragments.fragments.keys().cloned()) - .collect(); - - let target_fragment_ids = match fragments { - None => all_fragment_ids.into_iter().collect_vec(), - Some(fragment_ids) => { - let provide_fragment_ids: HashSet<_> = fragment_ids.into_iter().collect(); - if provide_fragment_ids - .iter() - .any(|fragment_id| !all_fragment_ids.contains(fragment_id)) - { - fail!( - "Invalid fragment ids: {:?}", - provide_fragment_ids - .difference(&all_fragment_ids) - .collect_vec() - ); - } - - provide_fragment_ids.into_iter().collect() - } - }; - - let policy = PbPolicy::StableResizePolicy(StableResizePolicy { - fragment_worker_changes: target_fragment_ids - .iter() - .map(|id| (*id as FragmentId, worker_changes.clone())) - .collect(), - }); - - let response = meta_client.get_reschedule_plan(policy, revision).await; - - let GetReschedulePlanResponse { - revision, - reschedules, - success, - } = match response { - Ok(response) => response, - Err(e) => { - fail!("Failed to generate plan: {}", e.as_report()); - } - }; - - if !success { - fail!("Failed to generate plan, current revision is {}", revision); - } - - if reschedules.is_empty() { - println!( - "No reschedule plan generated, no action required, current revision is {}", - revision - ); - return Ok(()); - } - - println!( - "Successfully generated plan, current revision is {}", - revision - ); - - if generate { - let payload = ReschedulePayload { - reschedule_revision: revision, - reschedule_plan: reschedules - .into_iter() - .map(|(fragment_id, reschedule)| (fragment_id, reschedule.into())) - .collect(), - }; - - if let Some(output) = output.as_ref() { - println!("Writing plan to file: {}", output); - let writer = std::fs::File::create(output)?; - serde_yaml::to_writer(writer, &payload)?; - println!("Writing plan to file: {} done", output); - println!("You can use the `risectl meta reschedule --from {}` command to execute the generated plan", output); - } else { - println!("Option `--output` is not provided, the result plan will be output to the current command line."); - println!("#=========== Payload ==============#"); - serde_yaml::to_writer(std::io::stdout(), &payload)?; - println!("#=========== Payload ==============#"); - } - } else { - if !yes { - match Confirm::new("Will perform actions on the cluster, are you sure?") - .with_default(false) - .with_help_message("Use the --generate flag to view the generated plan. Use the --yes or -y option to skip this prompt") - .with_placeholder("no") - .prompt() - { - Ok(true) => println!("Processing..."), - Ok(false) => { - fail!("Abort."); - } - Err(_) => { - fail!("Error with questionnaire, try again later"); - } - } - } - - let (success, next_revision) = - match meta_client.reschedule(reschedules, revision, false).await { - Ok(response) => response, - Err(e) => { - fail!("Failed to execute plan: {}", e.as_report()); - } - }; - - if !success { - fail!("Failed to execute plan, current revision is {}", revision); - } - - println!( - "Successfully executed plan, current revision is {}", - next_revision - ); - } - - Ok(()) -} - pub async fn update_schedulability( context: &CtlContext, workers: Vec, diff --git a/src/ctl/src/cmd_impl/table/scan.rs b/src/ctl/src/cmd_impl/table/scan.rs index 8c21d975009fe..0689e315f74cb 100644 --- a/src/ctl/src/cmd_impl/table/scan.rs +++ b/src/ctl/src/cmd_impl/table/scan.rs @@ -86,19 +86,35 @@ pub fn make_storage_table( )) } -pub async fn scan(context: &CtlContext, mv_name: String, data_dir: Option) -> Result<()> { +pub async fn scan( + context: &CtlContext, + mv_name: String, + data_dir: Option, + use_new_object_prefix_strategy: bool, +) -> Result<()> { let meta_client = context.meta_client().await?; let hummock = context - .hummock_store(HummockServiceOpts::from_env(data_dir)?) + .hummock_store(HummockServiceOpts::from_env( + data_dir, + use_new_object_prefix_strategy, + )?) .await?; let table = get_table_catalog(meta_client, mv_name).await?; do_scan(table, hummock).await } -pub async fn scan_id(context: &CtlContext, table_id: u32, data_dir: Option) -> Result<()> { +pub async fn scan_id( + context: &CtlContext, + table_id: u32, + data_dir: Option, + use_new_object_prefix_strategy: bool, +) -> Result<()> { let meta_client = context.meta_client().await?; let hummock = context - .hummock_store(HummockServiceOpts::from_env(data_dir)?) + .hummock_store(HummockServiceOpts::from_env( + data_dir, + use_new_object_prefix_strategy, + )?) .await?; let table = get_table_catalog_by_id(meta_client, table_id).await?; do_scan(table, hummock).await diff --git a/src/ctl/src/common/context.rs b/src/ctl/src/common/context.rs index ea4dfe07ada6a..2665a482a1074 100644 --- a/src/ctl/src/common/context.rs +++ b/src/ctl/src/common/context.rs @@ -15,7 +15,6 @@ use risingwave_rpc_client::MetaClient; use risingwave_storage::hummock::HummockStorage; use risingwave_storage::monitor::MonitoredStateStore; -use thiserror_ext::AsReport; use tokio::sync::OnceCell; use crate::common::hummock_service::{HummockServiceOpts, Metrics}; @@ -61,19 +60,10 @@ impl CtlContext { .cloned() } - pub async fn try_close(mut self) { + pub async fn try_close(&self) { tracing::info!("clean up context"); - if let Some(meta_client) = self.meta_client.take() { - if let Err(e) = meta_client - .unregister(meta_client.host_addr().clone()) - .await - { - tracing::warn!( - error = %e.as_report(), - worker_id = %meta_client.worker_id(), - "failed to unregister ctl worker", - ); - } + if let Some(meta_client) = self.meta_client.get() { + meta_client.try_unregister().await; } } } diff --git a/src/ctl/src/common/hummock_service.rs b/src/ctl/src/common/hummock_service.rs index e10d3669af601..e885548d5a1e1 100644 --- a/src/ctl/src/common/hummock_service.rs +++ b/src/ctl/src/common/hummock_service.rs @@ -17,11 +17,12 @@ use std::sync::Arc; use std::time::Duration; use anyhow::{anyhow, bail, Result}; -use risingwave_common::config::{EvictionConfig, MetricLevel, ObjectStoreConfig}; +use foyer::HybridCacheBuilder; +use risingwave_common::config::{MetricLevel, ObjectStoreConfig}; use risingwave_object_store::object::build_remote_object_store; use risingwave_rpc_client::MetaClient; use risingwave_storage::hummock::hummock_meta_client::MonitoredHummockMetaClient; -use risingwave_storage::hummock::{FileCache, HummockStorage, SstableStore, SstableStoreConfig}; +use risingwave_storage::hummock::{HummockStorage, SstableStore, SstableStoreConfig}; use risingwave_storage::monitor::{ global_hummock_state_store_metrics, CompactorMetrics, HummockMetrics, HummockStateStoreMetrics, MonitoredStateStore, MonitoredStorageMetrics, ObjectStoreMetrics, @@ -35,6 +36,8 @@ pub struct HummockServiceOpts { pub hummock_url: String, pub data_dir: Option, + use_new_object_prefix_strategy: bool, + heartbeat_handle: Option>, heartbeat_shutdown_sender: Option>, } @@ -54,7 +57,10 @@ impl HummockServiceOpts { /// Currently, we will read these variables for meta: /// /// * `RW_HUMMOCK_URL`: hummock store address - pub fn from_env(data_dir: Option) -> Result { + pub fn from_env( + data_dir: Option, + use_new_object_prefix_strategy: bool, + ) -> Result { let hummock_url = match env::var("RW_HUMMOCK_URL") { Ok(url) => { if !url.starts_with("hummock+") { @@ -79,11 +85,13 @@ impl HummockServiceOpts { bail!(MESSAGE); } }; + Ok(Self { hummock_url, data_dir, heartbeat_handle: None, heartbeat_shutdown_sender: None, + use_new_object_prefix_strategy, }) } @@ -141,6 +149,7 @@ impl HummockServiceOpts { metrics.storage_metrics.clone(), metrics.compactor_metrics.clone(), None, + self.use_new_object_prefix_strategy, ) .await?; @@ -156,34 +165,45 @@ impl HummockServiceOpts { } } - pub async fn create_sstable_store(&self) -> Result> { + pub async fn create_sstable_store( + &self, + use_new_object_prefix_strategy: bool, + ) -> Result> { let object_store = build_remote_object_store( self.hummock_url.strip_prefix("hummock+").unwrap(), Arc::new(ObjectStoreMetrics::unused()), "Hummock", - ObjectStoreConfig::default(), + Arc::new(ObjectStoreConfig::default()), ) .await; let opts = self.get_storage_opts(); + let meta_cache = HybridCacheBuilder::new() + .memory(opts.meta_cache_capacity_mb * (1 << 20)) + .with_shards(opts.meta_cache_shard_num) + .storage() + .build() + .await?; + let block_cache = HybridCacheBuilder::new() + .memory(opts.block_cache_capacity_mb * (1 << 20)) + .with_shards(opts.block_cache_shard_num) + .storage() + .build() + .await?; + Ok(Arc::new(SstableStore::new(SstableStoreConfig { store: Arc::new(object_store), path: opts.data_directory, - block_cache_capacity: opts.block_cache_capacity_mb * (1 << 20), - meta_cache_capacity: opts.meta_cache_capacity_mb * (1 << 20), - block_cache_shard_num: opts.block_cache_shard_num, - meta_cache_shard_num: opts.meta_cache_shard_num, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_eviction: EvictionConfig::for_test(), prefetch_buffer_capacity: opts.block_cache_capacity_mb * (1 << 20), max_prefetch_block_number: opts.max_prefetch_block_number, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), recent_filter: None, state_store_metrics: Arc::new(global_hummock_state_store_metrics( MetricLevel::Disabled, )), + use_new_object_prefix_strategy, + meta_cache, + block_cache, }))) } } diff --git a/src/ctl/src/lib.rs b/src/ctl/src/lib.rs index a1aaa8f48c5f5..7242ba1d3c74a 100644 --- a/src/ctl/src/lib.rs +++ b/src/ctl/src/lib.rs @@ -20,8 +20,10 @@ use clap::{Args, Parser, Subcommand}; use cmd_impl::bench::BenchCommands; use cmd_impl::hummock::SstDumpArgs; use itertools::Itertools; +use risingwave_common::util::tokio_util::sync::CancellationToken; use risingwave_hummock_sdk::HummockEpoch; use risingwave_meta::backup_restore::RestoreOpts; +use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::CompressionAlgorithm; use risingwave_pb::meta::update_worker_node_schedulability_request::Schedulability; use thiserror_ext::AsReport; @@ -192,6 +194,9 @@ enum HummockCommands { // data directory for hummock state store. None: use default data_dir: Option, + + #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")] + use_new_object_prefix_strategy: bool, }, SstDump(SstDumpArgs), /// trigger a targeted compaction through `compaction_group_id` @@ -254,6 +259,12 @@ enum HummockCommands { enable_emergency_picker: Option, #[clap(long)] tombstone_reclaim_ratio: Option, + #[clap(long)] + compression_level: Option, + #[clap(long)] + compression_algorithm: Option, + #[clap(long)] + max_l0_compact_level: Option, }, /// Split given compaction group into two. Moves the given tables to the new group. SplitCompactionGroup { @@ -295,6 +306,8 @@ enum HummockCommands { /// KVs that are matched with the user key are printed. #[clap(long)] user_key: String, + #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")] + use_new_object_prefix_strategy: bool, }, PrintVersionDeltaInArchive { /// The ident of the archive file in object store. It's also the first Hummock version id of this archive. @@ -306,6 +319,22 @@ enum HummockCommands { /// Version deltas that are related to the SST id are printed. #[clap(long)] sst_id: u64, + #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")] + use_new_object_prefix_strategy: bool, + }, + TieredCacheTracing { + #[clap(long)] + enable: bool, + #[clap(long)] + record_hybrid_insert_threshold_ms: Option, + #[clap(long)] + record_hybrid_get_threshold_ms: Option, + #[clap(long)] + record_hybrid_obtain_threshold_ms: Option, + #[clap(long)] + record_hybrid_remove_threshold_ms: Option, + #[clap(long)] + record_hybrid_fetch_threshold_ms: Option, }, } @@ -317,6 +346,9 @@ enum TableCommands { mv_name: String, // data directory for hummock state store. None: use default data_dir: Option, + + #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")] + use_new_object_prefix_strategy: bool, }, /// scan a state table using Id ScanById { @@ -324,6 +356,8 @@ enum TableCommands { table_id: u32, // data directory for hummock state store. None: use default data_dir: Option, + #[clap(short, long = "use-new-object-prefix-strategy", default_value = "true")] + use_new_object_prefix_strategy: bool, }, /// list all state tables List, @@ -406,15 +440,6 @@ pub struct ScaleVerticalCommands { #[derive(Subcommand, Debug)] enum ScaleCommands { - /// Scale the compute nodes horizontally, alias of `horizon` - Resize(ScaleHorizonCommands), - - /// Scale the compute nodes horizontally - Horizon(ScaleHorizonCommands), - - /// Scale the compute nodes vertically - Vertical(ScaleVerticalCommands), - /// Mark a compute node as unschedulable #[clap(verbatim_doc_comment)] Cordon { @@ -441,6 +466,7 @@ enum ScaleCommands { } #[derive(Subcommand)] +#[allow(clippy::large_enum_variant)] enum MetaCommands { /// pause the stream graph Pause, @@ -589,22 +615,33 @@ pub enum ProfileCommands { } /// Start `risectl` with the given options. +/// Cancel the operation when the given `shutdown` token triggers. /// Log and abort the process if any error occurs. /// /// Note: use [`start_fallible`] if you want to call functionalities of `risectl` /// in an embedded manner. -pub async fn start(opts: CliOpts) { - if let Err(e) = start_fallible(opts).await { - eprintln!("Error: {:#?}", e.as_report()); // pretty with backtrace - std::process::exit(1); +pub async fn start(opts: CliOpts, shutdown: CancellationToken) { + let context = CtlContext::default(); + + tokio::select! { + _ = shutdown.cancelled() => { + // Shutdown requested, clean up the context and return. + context.try_close().await; + } + + result = start_fallible(opts, &context) => { + if let Err(e) = result { + eprintln!("Error: {:#?}", e.as_report()); // pretty with backtrace + std::process::exit(1); + } + } } } /// Start `risectl` with the given options. /// Return `Err` if any error occurs. -pub async fn start_fallible(opts: CliOpts) -> Result<()> { - let context = CtlContext::default(); - let result = start_impl(opts, &context).await; +pub async fn start_fallible(opts: CliOpts, context: &CtlContext) -> Result<()> { + let result = start_impl(opts, context).await; context.try_close().await; result } @@ -633,8 +670,16 @@ async fn start_impl(opts: CliOpts, context: &CtlContext) -> Result<()> { epoch, table_id, data_dir, + use_new_object_prefix_strategy, }) => { - cmd_impl::hummock::list_kv(context, epoch, table_id, data_dir).await?; + cmd_impl::hummock::list_kv( + context, + epoch, + table_id, + data_dir, + use_new_object_prefix_strategy, + ) + .await?; } Commands::Hummock(HummockCommands::SstDump(args)) => { cmd_impl::hummock::sst_dump(context, args).await.unwrap() @@ -683,6 +728,9 @@ async fn start_impl(opts: CliOpts, context: &CtlContext) -> Result<()> { level0_overlapping_sub_level_compact_level_count, enable_emergency_picker, tombstone_reclaim_ratio, + compression_level, + compression_algorithm, + max_l0_compact_level, }) => { cmd_impl::hummock::update_compaction_config( context, @@ -703,6 +751,16 @@ async fn start_impl(opts: CliOpts, context: &CtlContext) -> Result<()> { level0_overlapping_sub_level_compact_level_count, enable_emergency_picker, tombstone_reclaim_ratio, + if let Some(level) = compression_level { + assert!(compression_algorithm.is_some()); + Some(CompressionAlgorithm { + level, + compression_algorithm: compression_algorithm.unwrap(), + }) + } else { + None + }, + max_l0_compact_level, ), ) .await? @@ -744,12 +802,14 @@ async fn start_impl(opts: CliOpts, context: &CtlContext) -> Result<()> { archive_ids, data_dir, sst_id, + use_new_object_prefix_strategy, }) => { cmd_impl::hummock::print_version_delta_in_archive( context, archive_ids, data_dir, sst_id, + use_new_object_prefix_strategy, ) .await?; } @@ -757,15 +817,51 @@ async fn start_impl(opts: CliOpts, context: &CtlContext) -> Result<()> { archive_ids, data_dir, user_key, + use_new_object_prefix_strategy, }) => { - cmd_impl::hummock::print_user_key_in_archive(context, archive_ids, data_dir, user_key) - .await?; + cmd_impl::hummock::print_user_key_in_archive( + context, + archive_ids, + data_dir, + user_key, + use_new_object_prefix_strategy, + ) + .await?; } - Commands::Table(TableCommands::Scan { mv_name, data_dir }) => { - cmd_impl::table::scan(context, mv_name, data_dir).await? + Commands::Hummock(HummockCommands::TieredCacheTracing { + enable, + record_hybrid_insert_threshold_ms, + record_hybrid_get_threshold_ms, + record_hybrid_obtain_threshold_ms, + record_hybrid_remove_threshold_ms, + record_hybrid_fetch_threshold_ms, + }) => { + cmd_impl::hummock::tiered_cache_tracing( + context, + enable, + record_hybrid_insert_threshold_ms, + record_hybrid_get_threshold_ms, + record_hybrid_obtain_threshold_ms, + record_hybrid_remove_threshold_ms, + record_hybrid_fetch_threshold_ms, + ) + .await? } - Commands::Table(TableCommands::ScanById { table_id, data_dir }) => { - cmd_impl::table::scan_id(context, table_id, data_dir).await? + Commands::Table(TableCommands::Scan { + mv_name, + data_dir, + use_new_object_prefix_strategy, + }) => { + cmd_impl::table::scan(context, mv_name, data_dir, use_new_object_prefix_strategy) + .await? + } + Commands::Table(TableCommands::ScanById { + table_id, + data_dir, + use_new_object_prefix_strategy, + }) => { + cmd_impl::table::scan_id(context, table_id, data_dir, use_new_object_prefix_strategy) + .await? } Commands::Table(TableCommands::List) => cmd_impl::table::list(context).await?, Commands::Bench(cmd) => cmd_impl::bench::do_bench(context, cmd).await?, @@ -849,13 +945,6 @@ async fn start_impl(opts: CliOpts, context: &CtlContext) -> Result<()> { Commands::Profile(ProfileCommands::Heap { dir }) => { cmd_impl::profile::heap_profile(context, dir).await? } - Commands::Scale(ScaleCommands::Horizon(resize)) - | Commands::Scale(ScaleCommands::Resize(resize)) => { - cmd_impl::scale::resize(context, resize.into()).await? - } - Commands::Scale(ScaleCommands::Vertical(resize)) => { - cmd_impl::scale::resize(context, resize.into()).await? - } Commands::Scale(ScaleCommands::Cordon { workers }) => { cmd_impl::scale::update_schedulability(context, workers, Schedulability::Unschedulable) .await? diff --git a/src/dml/src/dml_manager.rs b/src/dml/src/dml_manager.rs index f6611b6c94c21..d3502f1f9d2de 100644 --- a/src/dml/src/dml_manager.rs +++ b/src/dml/src/dml_manager.rs @@ -170,7 +170,6 @@ mod tests { use risingwave_common::array::StreamChunk; use risingwave_common::catalog::INITIAL_TABLE_VERSION_ID; use risingwave_common::test_prelude::StreamChunkTestExt; - use risingwave_common::transaction::transaction_id::TxnId; use risingwave_common::types::DataType; use super::*; diff --git a/src/dml/src/table.rs b/src/dml/src/table.rs index 4981ebce2e8a3..a5d0ecf8c23a4 100644 --- a/src/dml/src/table.rs +++ b/src/dml/src/table.rs @@ -283,14 +283,11 @@ impl TableStreamReader { #[cfg(test)] mod tests { - use std::sync::Arc; - use assert_matches::assert_matches; use futures::StreamExt; use itertools::Itertools; - use risingwave_common::array::{Array, I64Array, Op, StreamChunk}; + use risingwave_common::array::{Array, I64Array, Op}; use risingwave_common::catalog::ColumnId; - use risingwave_common::transaction::transaction_id::TxnId; use risingwave_common::types::DataType; use super::*; diff --git a/src/error/src/macros.rs b/src/error/src/macros.rs index b3079aa281ce9..16a11d2dba4d6 100644 --- a/src/error/src/macros.rs +++ b/src/error/src/macros.rs @@ -149,9 +149,6 @@ pub use must_match; #[cfg(test)] mod ensure_tests { - use std::convert::Into; - use std::result::Result::Err; - use anyhow::anyhow; use thiserror::Error; diff --git a/src/expr/core/Cargo.toml b/src/expr/core/Cargo.toml index 3f5ca590026db..cbff3a5ff2e28 100644 --- a/src/expr/core/Cargo.toml +++ b/src/expr/core/Cargo.toml @@ -15,18 +15,10 @@ ignored = ["workspace-hack", "ctor"] [package.metadata.cargo-udeps.ignore] normal = ["workspace-hack", "ctor"] -[features] -embedded-deno-udf = ["arrow-udf-js-deno"] -embedded-python-udf = ["arrow-udf-python"] - [dependencies] anyhow = "1" arrow-array = { workspace = true } arrow-schema = { workspace = true } -arrow-udf-js = { workspace = true } -arrow-udf-js-deno = { workspace = true, optional = true } -arrow-udf-python = { workspace = true, optional = true } -arrow-udf-wasm = { workspace = true } async-trait = "0.1" auto_impl = "1" await-tree = { workspace = true } @@ -38,7 +30,7 @@ chrono = { version = "0.4", default-features = false, features = [ const-currying = "0.0.4" downcast-rs = "1.2" easy-ext = "1" -educe = "0.5" +educe = "0.6" either = "1" enum-as-inner = "0.6" futures = "0.3" @@ -46,17 +38,14 @@ futures-async-stream = { workspace = true } futures-util = "0.3" itertools = { workspace = true } linkme = { version = "0.3", features = ["used_linker"] } -md5 = "0.7" -moka = { version = "0.12", features = ["sync"] } num-traits = "0.2" -openssl = { version = "0.10", features = ["vendored"] } parse-display = "0.9" paste = "1" +prometheus = "0.13" risingwave_common = { workspace = true } risingwave_common_estimate_size = { workspace = true } risingwave_expr_macro = { path = "../macro" } risingwave_pb = { workspace = true } -risingwave_udf = { workspace = true } smallvec = "1" static_assertions = "1" thiserror = "1" @@ -66,7 +55,6 @@ tokio = { version = "0.2", package = "madsim-tokio", features = [ "macros", ] } tracing = "0.1" -zstd = { version = "0.13", default-features = false } [target.'cfg(not(madsim))'.dependencies] workspace-hack = { path = "../../workspace-hack" } diff --git a/src/expr/core/src/aggregate/def.rs b/src/expr/core/src/aggregate/def.rs index 80293d98eaf11..5ca07d0153196 100644 --- a/src/expr/core/src/aggregate/def.rs +++ b/src/expr/core/src/aggregate/def.rs @@ -24,7 +24,7 @@ use risingwave_common::types::{DataType, Datum}; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_common::util::value_encoding::DatumFromProtoExt; use risingwave_pb::expr::agg_call::PbType; -use risingwave_pb::expr::{PbAggCall, PbInputRef}; +use risingwave_pb::expr::{PbAggCall, PbInputRef, PbUserDefinedFunctionMetadata}; use crate::expr::{ build_from_prost, BoxedExpression, ExpectExt, Expression, LiteralExpression, Token, @@ -32,6 +32,10 @@ use crate::expr::{ use crate::Result; /// Represents an aggregation function. +// TODO(runji): +// remove this struct from the expression module. +// this module only cares about aggregate functions themselves. +// advanced features like order by, filter, distinct, etc. should be handled by the upper layer. #[derive(Debug, Clone)] pub struct AggCall { /// Aggregation kind for constructing agg state. @@ -52,6 +56,9 @@ pub struct AggCall { /// Constant arguments. pub direct_args: Vec, + + /// Additional metadata for user defined functions. + pub user_defined: Option, } impl AggCall { @@ -90,6 +97,7 @@ impl AggCall { filter, distinct: agg_call.distinct, direct_args, + user_defined: agg_call.udf.clone(), }) } @@ -148,17 +156,16 @@ impl> Parser { AggCall { kind: AggKind::from_protobuf(func).unwrap(), - args: match children.as_slice() { - [] => AggArgs::None, - [(i, t)] => AggArgs::Unary(t.clone(), *i), - [(i0, t0), (i1, t1)] => AggArgs::Binary([t0.clone(), t1.clone()], [*i0, *i1]), - _ => panic!("too many arguments for agg call"), + args: AggArgs { + data_types: children.iter().map(|(_, ty)| ty.clone()).collect(), + val_indices: children.iter().map(|(idx, _)| *idx).collect(), }, return_type: ty, column_orders, filter: None, distinct, direct_args: Vec::new(), + user_defined: None, } } @@ -236,6 +243,7 @@ pub enum AggKind { /// Return last seen one of the input values. InternalLastSeenValue, + UserDefined, } impl AggKind { @@ -268,6 +276,7 @@ impl AggKind { PbType::Mode => Ok(AggKind::Mode), PbType::Grouping => Ok(AggKind::Grouping), PbType::InternalLastSeenValue => Ok(AggKind::InternalLastSeenValue), + PbType::UserDefined => Ok(AggKind::UserDefined), PbType::Unspecified => bail!("Unrecognized agg."), } } @@ -301,6 +310,7 @@ impl AggKind { Self::Mode => PbType::Mode, Self::Grouping => PbType::Grouping, Self::InternalLastSeenValue => PbType::InternalLastSeenValue, + Self::UserDefined => PbType::UserDefined, } } } @@ -409,6 +419,7 @@ pub mod agg_kinds { | AggKind::BoolOr | AggKind::BitAnd | AggKind::BitOr + | AggKind::UserDefined }; } pub use simply_cannot_two_phase; @@ -428,6 +439,7 @@ pub mod agg_kinds { | AggKind::BoolOr | AggKind::ApproxCountDistinct | AggKind::InternalLastSeenValue + | AggKind::UserDefined }; } pub use single_value_state; @@ -469,52 +481,40 @@ impl AggKind { } /// An aggregation function may accept 0, 1 or 2 arguments. -#[derive(Clone, Debug)] -pub enum AggArgs { - /// `None` is used for function calls that accept 0 argument, e.g. `count(*)`. - None, - /// `Unary` is used for function calls that accept 1 argument, e.g. `sum(x)`. - Unary(DataType, usize), - /// `Binary` is used for function calls that accept 2 arguments, e.g. `string_agg(x, delim)`. - Binary([DataType; 2], [usize; 2]), +#[derive(Clone, Debug, Default)] +pub struct AggArgs { + data_types: Box<[DataType]>, + val_indices: Box<[usize]>, } impl AggArgs { pub fn from_protobuf(args: &[PbInputRef]) -> Result { - let args = match args { - [] => Self::None, - [arg] => Self::Unary(DataType::from(arg.get_type()?), arg.get_index() as usize), - [arg1, arg2] => Self::Binary( - [ - DataType::from(arg1.get_type()?), - DataType::from(arg2.get_type()?), - ], - [arg1.get_index() as usize, arg2.get_index() as usize], - ), - _ => bail!("too many arguments for agg call"), - }; - Ok(args) + Ok(AggArgs { + data_types: args + .iter() + .map(|arg| DataType::from(arg.get_type().unwrap())) + .collect(), + val_indices: args.iter().map(|arg| arg.get_index() as usize).collect(), + }) } -} -impl AggArgs { /// return the types of arguments. pub fn arg_types(&self) -> &[DataType] { - use AggArgs::*; - match self { - None => &[], - Unary(typ, _) => std::slice::from_ref(typ), - Binary(typs, _) => typs, - } + &self.data_types } /// return the indices of the arguments in [`risingwave_common::array::StreamChunk`]. pub fn val_indices(&self) -> &[usize] { - use AggArgs::*; - match self { - None => &[], - Unary(_, val_idx) => std::slice::from_ref(val_idx), - Binary(_, val_indices) => val_indices, + &self.val_indices + } +} + +impl FromIterator<(DataType, usize)> for AggArgs { + fn from_iter>(iter: T) -> Self { + let (data_types, val_indices): (Vec<_>, Vec<_>) = iter.into_iter().unzip(); + AggArgs { + data_types: data_types.into(), + val_indices: val_indices.into(), } } } diff --git a/src/expr/core/src/aggregate/mod.rs b/src/expr/core/src/aggregate/mod.rs index 4e8666e8c9dee..2a1119d6fe301 100644 --- a/src/expr/core/src/aggregate/mod.rs +++ b/src/expr/core/src/aggregate/mod.rs @@ -15,6 +15,7 @@ use std::fmt::Debug; use std::ops::Range; +use anyhow::anyhow; use downcast_rs::{impl_downcast, Downcast}; use itertools::Itertools; use risingwave_common::array::StreamChunk; @@ -26,6 +27,8 @@ use crate::{ExprError, Result}; // aggregate definition mod def; +// user defined aggregate function +mod user_defined; pub use self::def::*; @@ -36,8 +39,8 @@ pub trait AggregateFunction: Send + Sync + 'static { fn return_type(&self) -> DataType; /// Creates an initial state of the aggregate function. - fn create_state(&self) -> AggregateState { - AggregateState::Datum(None) + fn create_state(&self) -> Result { + Ok(AggregateState::Datum(None)) } /// Update the state with multiple rows. @@ -58,7 +61,7 @@ pub trait AggregateFunction: Send + Sync + 'static { fn encode_state(&self, state: &AggregateState) -> Result { match state { AggregateState::Datum(d) => Ok(d.clone()), - _ => panic!("cannot encode state"), + AggregateState::Any(_) => Err(ExprError::Internal(anyhow!("cannot encode state"))), } } @@ -140,6 +143,10 @@ pub fn build_retractable(agg: &AggCall) -> Result { /// NOTE: This function ignores argument indices, `column_orders`, `filter` and `distinct` in /// `AggCall`. Such operations should be done in batch or streaming executors. pub fn build(agg: &AggCall, prefer_append_only: bool) -> Result { + if agg.kind == AggKind::UserDefined { + return user_defined::new_user_defined(agg); + } + let sig = crate::sig::FUNCTION_REGISTRY .get(agg.kind, agg.args.arg_types(), &agg.return_type) .ok_or_else(|| { diff --git a/src/expr/core/src/aggregate/user_defined.rs b/src/expr/core/src/aggregate/user_defined.rs new file mode 100644 index 0000000000000..d554a618a1c76 --- /dev/null +++ b/src/expr/core/src/aggregate/user_defined.rs @@ -0,0 +1,163 @@ +// Copyright 2024 RisingWave Labs +// +// 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 anyhow::Context; +use arrow_array::ArrayRef; +use arrow_schema::{Field, Fields, Schema, SchemaRef}; +use risingwave_common::array::arrow::{FromArrow, ToArrow, UdfArrowConvert}; +use risingwave_common::array::Op; +use risingwave_common::bitmap::Bitmap; + +use super::*; +use crate::sig::{UdfImpl, UdfKind, UdfOptions}; + +#[derive(Debug)] +pub struct UserDefinedAggregateFunction { + arg_schema: SchemaRef, + return_type: DataType, + return_field: Field, + state_field: Field, + runtime: Box, +} + +#[async_trait::async_trait] +impl AggregateFunction for UserDefinedAggregateFunction { + fn return_type(&self) -> DataType { + self.return_type.clone() + } + + /// Creates an initial state of the aggregate function. + fn create_state(&self) -> Result { + let state = self.runtime.call_agg_create_state()?; + Ok(AggregateState::Any(Box::new(State(state)))) + } + + /// Update the state with multiple rows. + async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { + let state = &mut state.downcast_mut::().0; + let ops = input + .visibility() + .iter_ones() + .map(|i| Some(matches!(input.ops()[i], Op::Delete | Op::UpdateDelete))) + .collect(); + // this will drop invisible rows + let arrow_input = UdfArrowConvert::default() + .to_record_batch(self.arg_schema.clone(), input.data_chunk())?; + let new_state = self + .runtime + .call_agg_accumulate_or_retract(state, &ops, &arrow_input)?; + *state = new_state; + Ok(()) + } + + /// Update the state with a range of rows. + async fn update_range( + &self, + state: &mut AggregateState, + input: &StreamChunk, + range: Range, + ) -> Result<()> { + // XXX(runji): this may be inefficient + let vis = input.visibility() & Bitmap::from_range(input.capacity(), range); + let input = input.clone_with_vis(vis); + self.update(state, &input).await + } + + /// Get aggregate result from the state. + async fn get_result(&self, state: &AggregateState) -> Result { + let state = &state.downcast_ref::().0; + let arrow_output = self.runtime.call_agg_finish(state)?; + let output = UdfArrowConvert::default().from_array(&self.return_field, &arrow_output)?; + Ok(output.datum_at(0)) + } + + /// Encode the state into a datum that can be stored in state table. + fn encode_state(&self, state: &AggregateState) -> Result { + let state = &state.downcast_ref::().0; + let state = UdfArrowConvert::default().from_array(&self.state_field, state)?; + Ok(state.datum_at(0)) + } + + /// Decode the state from a datum in state table. + fn decode_state(&self, datum: Datum) -> Result { + let array = { + let mut builder = DataType::Bytea.create_array_builder(1); + builder.append(datum); + builder.finish() + }; + let state = UdfArrowConvert::default().to_array(self.state_field.data_type(), &array)?; + Ok(AggregateState::Any(Box::new(State(state)))) + } +} + +// In arrow-udf, aggregate state is represented as an `ArrayRef`. +// To avoid unnecessary conversion between `ArrayRef` and `Datum`, +// we store `ArrayRef` directly in our `AggregateState`. +#[derive(Debug)] +struct State(ArrayRef); + +impl EstimateSize for State { + fn estimated_heap_size(&self) -> usize { + self.0.get_array_memory_size() + } +} + +impl AggStateDyn for State {} + +/// Create a new user-defined aggregate function. +pub fn new_user_defined(agg: &AggCall) -> Result { + let udf = agg + .user_defined + .as_ref() + .context("missing UDF definition")?; + + let identifier = udf.get_identifier()?; + let language = udf.language.as_str(); + let runtime = udf.runtime.as_deref(); + let link = udf.link.as_deref(); + + let build_fn = crate::sig::find_udf_impl(language, runtime, link)?.build_fn; + let runtime = build_fn(UdfOptions { + kind: UdfKind::Aggregate, + body: udf.body.as_deref(), + compressed_binary: udf.compressed_binary.as_deref(), + link: udf.link.as_deref(), + identifier, + arg_names: &udf.arg_names, + return_type: &agg.return_type, + always_retry_on_network_error: false, + function_type: udf.function_type.as_deref(), + }) + .context("failed to build UDF runtime")?; + + // legacy UDF runtimes do not support aggregate functions, + // so we can assume that the runtime is not legacy + let arrow_convert = UdfArrowConvert::default(); + let arg_schema = Arc::new(Schema::new( + udf.arg_types + .iter() + .map(|t| arrow_convert.to_arrow_field("", &DataType::from(t))) + .try_collect::<_, Fields, _>()?, + )); + + Ok(Box::new(UserDefinedAggregateFunction { + return_field: arrow_convert.to_arrow_field("", &agg.return_type)?, + state_field: Field::new("state", arrow_schema::DataType::Binary, true), + return_type: agg.return_type.clone(), + arg_schema, + runtime, + })) +} diff --git a/src/expr/core/src/codegen.rs b/src/expr/core/src/codegen.rs index c9022e40aec91..825889bf6fc77 100644 --- a/src/expr/core/src/codegen.rs +++ b/src/expr/core/src/codegen.rs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! This module contains imports that are used in the generated code for the `#[function]` macro. + pub use async_trait::async_trait; pub use futures_async_stream::try_stream; pub use futures_util::stream::BoxStream; -pub use itertools::multizip; pub use linkme; +pub use thiserror_ext::AsReport; diff --git a/src/expr/core/src/error.rs b/src/expr/core/src/error.rs index 6688824093d2d..e02c5f4521cf5 100644 --- a/src/expr/core/src/error.rs +++ b/src/expr/core/src/error.rs @@ -15,7 +15,7 @@ use std::fmt::{Debug, Display}; use risingwave_common::array::{ArrayError, ArrayRef}; -use risingwave_common::types::DataType; +use risingwave_common::types::{DataType, DatumRef, ToText}; use risingwave_pb::PbFieldNotFound; use thiserror::Error; use thiserror_ext::AsReport; @@ -95,13 +95,6 @@ pub enum ExprError { anyhow::Error, ), - #[error("UDF error: {0}")] - Udf( - #[from] - #[backtrace] - risingwave_udf::Error, - ), - #[error("not a constant")] NotConstant, @@ -117,26 +110,72 @@ pub enum ExprError { #[error("invalid state: {0}")] InvalidState(String), - #[error("error in cryptography: {0}")] - Cryptography(Box), -} - -#[derive(Debug)] -pub enum CryptographyStage { - Encrypt, - Decrypt, -} + /// Function error message returned by UDF. + #[error("{0}")] + Custom(String), -#[derive(Debug, Error)] -#[error("{stage:?} stage, reason: {reason}")] -pub struct CryptographyError { - pub stage: CryptographyStage, - #[source] - pub reason: openssl::error::ErrorStack, + /// Error from a function call. + /// + /// Use [`ExprError::function`] to create this error. + #[error("error while evaluating expression `{display}`")] + Function { + display: Box, + #[backtrace] + // We don't use `anyhow::Error` because we don't want to always capture the backtrace. + source: Box, + }, } static_assertions::const_assert_eq!(std::mem::size_of::(), 40); +impl ExprError { + /// Constructs a [`ExprError::Function`] error with the given information for display. + pub fn function<'a>( + fn_name: &str, + args: impl IntoIterator>, + source: impl Into>, + ) -> Self { + use std::fmt::Write; + + let display = { + let mut s = String::new(); + write!(s, "{}(", fn_name).unwrap(); + for (i, arg) in args.into_iter().enumerate() { + if i > 0 { + write!(s, ", ").unwrap(); + } + if let Some(arg) = arg { + // Act like `quote_literal(arg::varchar)`. + // Since this is mainly for debugging, we don't need to be too precise. + let arg = arg.to_text(); + if arg.contains('\\') { + // use escape format: E'...' + write!(s, "E").unwrap(); + } + write!(s, "'").unwrap(); + for c in arg.chars() { + match c { + '\'' => write!(s, "''").unwrap(), + '\\' => write!(s, "\\\\").unwrap(), + _ => write!(s, "{}", c).unwrap(), + } + } + write!(s, "'").unwrap(); + } else { + write!(s, "NULL").unwrap(); + } + } + write!(s, ")").unwrap(); + s + }; + + Self::Function { + display: display.into(), + source: source.into(), + } + } +} + impl From for ExprError { fn from(e: chrono::ParseError) -> Self { Self::Parse(e.to_report_string().into()) @@ -178,6 +217,12 @@ impl From> for MultiExprError { } } +impl FromIterator for MultiExprError { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + impl IntoIterator for MultiExprError { type IntoIter = std::vec::IntoIter; type Item = ExprError; diff --git a/src/expr/core/src/expr/and_or.rs b/src/expr/core/src/expr/and_or.rs index fac820a61b5b3..f265a8a208d29 100644 --- a/src/expr/core/src/expr/and_or.rs +++ b/src/expr/core/src/expr/and_or.rs @@ -142,9 +142,6 @@ fn or(l: Option, r: Option) -> Option { #[cfg(test)] mod tests { - use risingwave_common::array::DataChunk; - use risingwave_common::test_prelude::DataChunkTestExt; - use super::*; use crate::expr::build_from_pretty; diff --git a/src/expr/core/src/expr/expr_literal.rs b/src/expr/core/src/expr/expr_literal.rs index ff6fdb79197a2..e85e6f03af6d7 100644 --- a/src/expr/core/src/expr/expr_literal.rs +++ b/src/expr/core/src/expr/expr_literal.rs @@ -96,7 +96,6 @@ mod tests { use risingwave_pb::data::{PbDataType, PbDatum}; use risingwave_pb::expr::expr_node::RexNode::{self, Constant}; use risingwave_pb::expr::expr_node::Type; - use risingwave_pb::expr::ExprNode; use super::*; diff --git a/src/expr/core/src/expr/expr_some_all.rs b/src/expr/core/src/expr/expr_some_all.rs index 9250aa6d877cf..3f7bc7abbc9f5 100644 --- a/src/expr/core/src/expr/expr_some_all.rs +++ b/src/expr/core/src/expr/expr_some_all.rs @@ -272,7 +272,6 @@ impl Build for SomeAllExpression { #[cfg(test)] mod tests { - use risingwave_common::array::DataChunk; use risingwave_common::row::Row; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::ToOwnedDatum; diff --git a/src/expr/core/src/expr/expr_udf.rs b/src/expr/core/src/expr/expr_udf.rs index ed7d597cce52a..c5bce6a2944df 100644 --- a/src/expr/core/src/expr/expr_udf.rs +++ b/src/expr/core/src/expr/expr_udf.rs @@ -12,71 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; -use std::convert::TryFrom; -use std::sync::atomic::{AtomicU8, Ordering}; -use std::sync::{Arc, LazyLock, Weak}; -use std::time::Duration; - -use anyhow::{Context, Error}; -use arrow_schema::{Field, Fields, Schema}; -use arrow_udf_js::{CallMode as JsCallMode, Runtime as JsRuntime}; -#[cfg(feature = "embedded-deno-udf")] -use arrow_udf_js_deno::{CallMode as DenoCallMode, Runtime as DenoRuntime}; -#[cfg(feature = "embedded-python-udf")] -use arrow_udf_python::{CallMode as PythonCallMode, Runtime as PythonRuntime}; -use arrow_udf_wasm::Runtime as WasmRuntime; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, LazyLock}; + +use anyhow::Context; +use arrow_schema::{Fields, Schema, SchemaRef}; use await_tree::InstrumentAwait; -use cfg_or_panic::cfg_or_panic; -use moka::sync::Cache; -use risingwave_common::array::{ArrayError, ArrayRef, DataChunk}; +use prometheus::{exponential_buckets, Registry}; +use risingwave_common::array::arrow::{FromArrow, ToArrow, UdfArrowConvert}; +use risingwave_common::array::{Array, ArrayRef, DataChunk}; +use risingwave_common::metrics::*; +use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY; use risingwave_common::row::OwnedRow; use risingwave_common::types::{DataType, Datum}; use risingwave_expr::expr_context::FRAGMENT_ID; use risingwave_pb::expr::ExprNode; -use risingwave_udf::metrics::GLOBAL_METRICS; -use risingwave_udf::ArrowFlightUdfClient; -use thiserror_ext::AsReport; use super::{BoxedExpression, Build}; use crate::expr::Expression; -use crate::{bail, Result}; +use crate::sig::{UdfImpl, UdfKind, UdfOptions}; +use crate::{bail, ExprError, Result}; #[derive(Debug)] pub struct UserDefinedFunction { children: Vec, arg_types: Vec, return_type: DataType, - #[expect(dead_code)] - arg_schema: Arc, - imp: UdfImpl, - identifier: String, + arg_schema: SchemaRef, + runtime: Box, + arrow_convert: UdfArrowConvert, span: await_tree::Span, - /// Number of remaining successful calls until retry is enabled. - /// This parameter is designed to prevent continuous retry on every call, which would increase delay. - /// Logic: - /// It resets to `INITIAL_RETRY_COUNT` after a single failure and then decrements with each call, enabling retry when it reaches zero. - /// If non-zero, we will not retry on connection errors to prevent blocking the stream. - /// On each connection error, the count will be reset to `INITIAL_RETRY_COUNT`. - /// On each successful call, the count will be decreased by 1. - /// Link: - /// See . - disable_retry_count: AtomicU8, - /// Always retry. Overrides `disable_retry_count`. - always_retry_on_network_error: bool, -} - -const INITIAL_RETRY_COUNT: u8 = 16; - -#[derive(Debug)] -pub enum UdfImpl { - External(Arc), - Wasm(Arc), - JavaScript(JsRuntime), - #[cfg(feature = "embedded-python-udf")] - Python(PythonRuntime), - #[cfg(feature = "embedded-deno-udf")] - Deno(Arc), + metrics: Metrics, } #[async_trait::async_trait] @@ -117,103 +83,39 @@ impl Expression for UserDefinedFunction { impl UserDefinedFunction { async fn eval_inner(&self, input: &DataChunk) -> Result { // this will drop invisible rows - let arrow_input = arrow_array::RecordBatch::try_from(input)?; + let arrow_input = self + .arrow_convert + .to_record_batch(self.arg_schema.clone(), input)?; // metrics - let metrics = &*GLOBAL_METRICS; - // batch query does not have a fragment_id - let fragment_id = FRAGMENT_ID::try_with(ToOwned::to_owned) - .unwrap_or(0) - .to_string(); - let addr = match &self.imp { - UdfImpl::External(client) => client.get_addr(), - _ => "", - }; - let language = match &self.imp { - UdfImpl::Wasm(_) => "wasm", - UdfImpl::JavaScript(_) => "javascript(quickjs)", - #[cfg(feature = "embedded-python-udf")] - UdfImpl::Python(_) => "python", - #[cfg(feature = "embedded-deno-udf")] - UdfImpl::Deno(_) => "javascript(deno)", - UdfImpl::External(_) => "external", - }; - let labels: &[&str; 4] = &[addr, language, &self.identifier, fragment_id.as_str()]; - metrics - .udf_input_chunk_rows - .with_label_values(labels) + self.metrics + .input_chunk_rows .observe(arrow_input.num_rows() as f64); - metrics - .udf_input_rows - .with_label_values(labels) + self.metrics + .input_rows .inc_by(arrow_input.num_rows() as u64); - metrics - .udf_input_bytes - .with_label_values(labels) + self.metrics + .input_bytes .inc_by(arrow_input.get_array_memory_size() as u64); - let timer = metrics.udf_latency.with_label_values(labels).start_timer(); - - let arrow_output_result: Result = match &self.imp { - UdfImpl::Wasm(runtime) => runtime.call(&self.identifier, &arrow_input), - UdfImpl::JavaScript(runtime) => runtime.call(&self.identifier, &arrow_input), - #[cfg(feature = "embedded-python-udf")] - UdfImpl::Python(runtime) => runtime.call(&self.identifier, &arrow_input), - #[cfg(feature = "embedded-deno-udf")] - UdfImpl::Deno(runtime) => tokio::task::block_in_place(|| { - tokio::runtime::Handle::current() - .block_on(runtime.call(&self.identifier, arrow_input)) - }), - UdfImpl::External(client) => { - let disable_retry_count = self.disable_retry_count.load(Ordering::Relaxed); - let result = if self.always_retry_on_network_error { - client - .call_with_always_retry_on_network_error( - &self.identifier, - arrow_input, - &fragment_id, - ) - .instrument_await(self.span.clone()) - .await - } else { - let result = if disable_retry_count != 0 { - client - .call(&self.identifier, arrow_input) - .instrument_await(self.span.clone()) - .await - } else { - client - .call_with_retry(&self.identifier, arrow_input) - .instrument_await(self.span.clone()) - .await - }; - let disable_retry_count = self.disable_retry_count.load(Ordering::Relaxed); - let connection_error = matches!(&result, Err(e) if e.is_connection_error()); - if connection_error && disable_retry_count != INITIAL_RETRY_COUNT { - // reset count on connection error - self.disable_retry_count - .store(INITIAL_RETRY_COUNT, Ordering::Relaxed); - } else if !connection_error && disable_retry_count != 0 { - // decrease count on success, ignore if exchange failed - _ = self.disable_retry_count.compare_exchange( - disable_retry_count, - disable_retry_count - 1, - Ordering::Relaxed, - Ordering::Relaxed, - ); - } - result - }; - result.map_err(|e| e.into()) - } - }; + let timer = self.metrics.latency.start_timer(); + + let arrow_output_result = self + .runtime + .call(&arrow_input) + .instrument_await(self.span.clone()) + .await; + timer.stop_and_record(); if arrow_output_result.is_ok() { - &metrics.udf_success_count + &self.metrics.success_count } else { - &metrics.udf_failure_count + &self.metrics.failure_count } - .with_label_values(labels) .inc(); + // update memory usage + self.metrics + .memory_usage_bytes + .set(self.runtime.memory_usage() as i64); let arrow_output = arrow_output_result?; @@ -225,7 +127,7 @@ impl UserDefinedFunction { ); } - let output = DataChunk::try_from(&arrow_output)?; + let output = self.arrow_convert.from_record_batch(&arrow_output)?; let output = output.uncompact(input.visibility().clone()); let Some(array) = output.columns().first() else { @@ -239,6 +141,22 @@ impl UserDefinedFunction { ); } + // handle optional error column + if let Some(errors) = output.columns().get(1) { + if errors.data_type() != DataType::Varchar { + bail!( + "UDF returned errors column with invalid type: {:?}", + errors.data_type() + ); + } + let errors = errors + .as_utf8() + .iter() + .filter_map(|msg| msg.map(|s| ExprError::Custom(s.into()))) + .collect(); + return Err(crate::ExprError::Multiple(array.clone(), errors)); + } + Ok(array.clone()) } } @@ -250,171 +168,206 @@ impl Build for UserDefinedFunction { ) -> Result { let return_type = DataType::from(prost.get_return_type().unwrap()); let udf = prost.get_rex_node().unwrap().as_udf().unwrap(); + let identifier = udf.get_identifier()?; - #[cfg(not(feature = "embedded-deno-udf"))] - let runtime = "quickjs"; + let language = udf.language.as_str(); + let runtime = udf.runtime.as_deref(); + let link = udf.link.as_deref(); - #[cfg(feature = "embedded-deno-udf")] - let runtime = match udf.runtime.as_deref() { - Some("deno") => "deno", - _ => "quickjs", - }; + // lookup UDF builder + let build_fn = crate::sig::find_udf_impl(language, runtime, link)?.build_fn; + let runtime = build_fn(UdfOptions { + kind: UdfKind::Scalar, + body: udf.body.as_deref(), + compressed_binary: udf.compressed_binary.as_deref(), + link: udf.link.as_deref(), + identifier, + arg_names: &udf.arg_names, + return_type: &return_type, + always_retry_on_network_error: udf.always_retry_on_network_error, + function_type: udf.function_type.as_deref(), + }) + .context("failed to build UDF runtime")?; - let identifier = udf.get_identifier()?; - let imp = match udf.language.as_str() { - #[cfg(not(madsim))] - "wasm" | "rust" => { - let compressed_wasm_binary = udf.get_compressed_binary()?; - let wasm_binary = zstd::stream::decode_all(compressed_wasm_binary.as_slice()) - .context("failed to decompress wasm binary")?; - let runtime = get_or_create_wasm_runtime(&wasm_binary)?; - UdfImpl::Wasm(runtime) - } - "javascript" if runtime != "deno" => { - let mut rt = JsRuntime::new()?; - let body = format!( - "export function {}({}) {{ {} }}", - identifier, - udf.arg_names.join(","), - udf.get_body()? - ); - rt.add_function( - identifier, - arrow_schema::DataType::try_from(&return_type)?, - JsCallMode::CalledOnNullInput, - &body, - )?; - UdfImpl::JavaScript(rt) - } - #[cfg(feature = "embedded-deno-udf")] - "javascript" if runtime == "deno" => { - let rt = DenoRuntime::new(); - let body = match udf.get_body() { - Ok(body) => body.clone(), - Err(_) => match udf.get_compressed_binary() { - Ok(compressed_binary) => { - let binary = zstd::stream::decode_all(compressed_binary.as_slice()) - .context("failed to decompress binary")?; - String::from_utf8(binary).context("failed to decode binary")? - } - Err(_) => { - bail!("UDF body or compressed binary is required for deno UDF"); - } - }, - }; - - let body = if matches!(udf.function_type.as_deref(), Some("async")) { - format!( - "export async function {}({}) {{ {} }}", - identifier, - udf.arg_names.join(","), - body - ) - } else { - format!( - "export function {}({}) {{ {} }}", - identifier, - udf.arg_names.join(","), - body - ) - }; - - futures::executor::block_on(rt.add_function( - identifier, - arrow_schema::DataType::try_from(&return_type)?, - DenoCallMode::CalledOnNullInput, - &body, - ))?; - - UdfImpl::Deno(rt) - } - #[cfg(feature = "embedded-python-udf")] - "python" if udf.body.is_some() => { - let mut rt = PythonRuntime::builder().sandboxed(true).build()?; - let body = udf.get_body()?; - rt.add_function( - identifier, - arrow_schema::DataType::try_from(&return_type)?, - PythonCallMode::CalledOnNullInput, - body, - )?; - UdfImpl::Python(rt) - } - #[cfg(not(madsim))] - _ => { - let link = udf.get_link()?; - UdfImpl::External(get_or_create_flight_client(link)?) - } - #[cfg(madsim)] - l => panic!("UDF language {l:?} is not supported on madsim"), + let arrow_convert = UdfArrowConvert { + legacy: runtime.is_legacy(), }; let arg_schema = Arc::new(Schema::new( udf.arg_types .iter() - .map::, _>(|t| { - Ok(Field::new( - "", - DataType::from(t).try_into().map_err(|e: ArrayError| { - risingwave_udf::Error::unsupported(e.to_report_string()) - })?, - true, - )) - }) + .map(|t| arrow_convert.to_arrow_field("", &DataType::from(t))) .try_collect::()?, )); + let metrics = GLOBAL_METRICS.with_label_values( + link.unwrap_or(""), + language, + identifier, + // batch query does not have a fragment_id + &FRAGMENT_ID::try_with(ToOwned::to_owned) + .unwrap_or(0) + .to_string(), + ); + Ok(Self { children: udf.children.iter().map(build_child).try_collect()?, arg_types: udf.arg_types.iter().map(|t| t.into()).collect(), return_type, arg_schema, - imp, - identifier: identifier.clone(), + runtime, + arrow_convert, span: format!("udf_call({})", identifier).into(), - disable_retry_count: AtomicU8::new(0), - always_retry_on_network_error: udf.always_retry_on_network_error, + metrics, }) } } -#[cfg(not(madsim))] -/// Get or create a client for the given UDF service. -/// -/// There is a global cache for clients, so that we can reuse the same client for the same service. -pub(crate) fn get_or_create_flight_client(link: &str) -> Result> { - static CLIENTS: LazyLock>>> = - LazyLock::new(Default::default); - let mut clients = CLIENTS.lock().unwrap(); - if let Some(client) = clients.get(link).and_then(|c| c.upgrade()) { - // reuse existing client - Ok(client) - } else { - // create new client - let client = Arc::new(ArrowFlightUdfClient::connect_lazy(link)?); - clients.insert(link.into(), Arc::downgrade(&client)); - Ok(client) - } +/// Monitor metrics for UDF. +#[derive(Debug, Clone)] +struct MetricsVec { + /// Number of successful UDF calls. + success_count: LabelGuardedIntCounterVec<4>, + /// Number of failed UDF calls. + failure_count: LabelGuardedIntCounterVec<4>, + /// Total number of retried UDF calls. + retry_count: LabelGuardedIntCounterVec<4>, + /// Input chunk rows of UDF calls. + input_chunk_rows: LabelGuardedHistogramVec<4>, + /// The latency of UDF calls in seconds. + latency: LabelGuardedHistogramVec<4>, + /// Total number of input rows of UDF calls. + input_rows: LabelGuardedIntCounterVec<4>, + /// Total number of input bytes of UDF calls. + input_bytes: LabelGuardedIntCounterVec<4>, + /// Total memory usage of UDF runtime in bytes. + memory_usage_bytes: LabelGuardedIntGaugeVec<5>, } -/// Get or create a wasm runtime. -/// -/// Runtimes returned by this function are cached inside for at least 60 seconds. -/// Later calls with the same binary will reuse the same runtime. -#[cfg_or_panic(not(madsim))] -pub fn get_or_create_wasm_runtime(binary: &[u8]) -> Result> { - static RUNTIMES: LazyLock>> = LazyLock::new(|| { - Cache::builder() - .time_to_idle(Duration::from_secs(60)) - .build() - }); - - let md5 = md5::compute(binary); - if let Some(runtime) = RUNTIMES.get(&md5) { - return Ok(runtime.clone()); +/// Monitor metrics for UDF. +#[derive(Debug, Clone)] +struct Metrics { + /// Number of successful UDF calls. + success_count: LabelGuardedIntCounter<4>, + /// Number of failed UDF calls. + failure_count: LabelGuardedIntCounter<4>, + /// Total number of retried UDF calls. + #[allow(dead_code)] + retry_count: LabelGuardedIntCounter<4>, + /// Input chunk rows of UDF calls. + input_chunk_rows: LabelGuardedHistogram<4>, + /// The latency of UDF calls in seconds. + latency: LabelGuardedHistogram<4>, + /// Total number of input rows of UDF calls. + input_rows: LabelGuardedIntCounter<4>, + /// Total number of input bytes of UDF calls. + input_bytes: LabelGuardedIntCounter<4>, + /// Total memory usage of UDF runtime in bytes. + memory_usage_bytes: LabelGuardedIntGauge<5>, +} + +/// Global UDF metrics. +static GLOBAL_METRICS: LazyLock = + LazyLock::new(|| MetricsVec::new(&GLOBAL_METRICS_REGISTRY)); + +impl MetricsVec { + fn new(registry: &Registry) -> Self { + let labels = &["link", "language", "name", "fragment_id"]; + let labels5 = &["link", "language", "name", "fragment_id", "instance_id"]; + let success_count = register_guarded_int_counter_vec_with_registry!( + "udf_success_count", + "Total number of successful UDF calls", + labels, + registry + ) + .unwrap(); + let failure_count = register_guarded_int_counter_vec_with_registry!( + "udf_failure_count", + "Total number of failed UDF calls", + labels, + registry + ) + .unwrap(); + let retry_count = register_guarded_int_counter_vec_with_registry!( + "udf_retry_count", + "Total number of retried UDF calls", + labels, + registry + ) + .unwrap(); + let input_chunk_rows = register_guarded_histogram_vec_with_registry!( + "udf_input_chunk_rows", + "Input chunk rows of UDF calls", + labels, + exponential_buckets(1.0, 2.0, 10).unwrap(), // 1 to 1024 + registry + ) + .unwrap(); + let latency = register_guarded_histogram_vec_with_registry!( + "udf_latency", + "The latency(s) of UDF calls", + labels, + exponential_buckets(0.000001, 2.0, 30).unwrap(), // 1us to 1000s + registry + ) + .unwrap(); + let input_rows = register_guarded_int_counter_vec_with_registry!( + "udf_input_rows", + "Total number of input rows of UDF calls", + labels, + registry + ) + .unwrap(); + let input_bytes = register_guarded_int_counter_vec_with_registry!( + "udf_input_bytes", + "Total number of input bytes of UDF calls", + labels, + registry + ) + .unwrap(); + let memory_usage_bytes = register_guarded_int_gauge_vec_with_registry!( + "udf_memory_usage", + "Total memory usage of UDF runtime in bytes", + labels5, + registry + ) + .unwrap(); + + MetricsVec { + success_count, + failure_count, + retry_count, + input_chunk_rows, + latency, + input_rows, + input_bytes, + memory_usage_bytes, + } } - let runtime = Arc::new(arrow_udf_wasm::Runtime::new(binary)?); - RUNTIMES.insert(md5, runtime.clone()); - Ok(runtime) + fn with_label_values( + &self, + link: &str, + language: &str, + identifier: &str, + fragment_id: &str, + ) -> Metrics { + // generate an unique id for each instance + static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(0); + let instance_id = NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed).to_string(); + + let labels = &[link, language, identifier, fragment_id]; + let labels5 = &[link, language, identifier, fragment_id, &instance_id]; + + Metrics { + success_count: self.success_count.with_guarded_label_values(labels), + failure_count: self.failure_count.with_guarded_label_values(labels), + retry_count: self.retry_count.with_guarded_label_values(labels), + input_chunk_rows: self.input_chunk_rows.with_guarded_label_values(labels), + latency: self.latency.with_guarded_label_values(labels), + input_rows: self.input_rows.with_guarded_label_values(labels), + input_bytes: self.input_bytes.with_guarded_label_values(labels), + memory_usage_bytes: self.memory_usage_bytes.with_guarded_label_values(labels5), + } + } } diff --git a/src/expr/core/src/expr/mod.rs b/src/expr/core/src/expr/mod.rs index 6dbb3906f5618..3f4b07d86bedf 100644 --- a/src/expr/core/src/expr/mod.rs +++ b/src/expr/core/src/expr/mod.rs @@ -51,7 +51,6 @@ use risingwave_common::types::{DataType, Datum}; pub use self::build::*; pub use self::expr_input_ref::InputRefExpression; pub use self::expr_literal::LiteralExpression; -pub use self::expr_udf::get_or_create_wasm_runtime; pub use self::value::{ValueImpl, ValueRef}; pub use self::wrapper::*; pub use super::{ExprError, Result}; diff --git a/src/expr/core/src/lib.rs b/src/expr/core/src/lib.rs index da89a4207cfc5..b250b8ce901f5 100644 --- a/src/expr/core/src/lib.rs +++ b/src/expr/core/src/lib.rs @@ -35,6 +35,6 @@ pub mod sig; pub mod table_function; pub mod window_function; -pub use error::{ContextUnavailable, CryptographyError, CryptographyStage, ExprError, Result}; +pub use error::{ContextUnavailable, ExprError, Result}; pub use risingwave_common::{bail, ensure}; pub use risingwave_expr_macro::*; diff --git a/src/expr/core/src/sig/mod.rs b/src/expr/core/src/sig/mod.rs index 747779f81e990..124a002f6519e 100644 --- a/src/expr/core/src/sig/mod.rs +++ b/src/expr/core/src/sig/mod.rs @@ -30,6 +30,10 @@ use crate::expr::BoxedExpression; use crate::table_function::BoxedTableFunction; use crate::ExprError; +mod udf; + +pub use self::udf::*; + /// The global registry of all function signatures. pub static FUNCTION_REGISTRY: LazyLock = LazyLock::new(|| { let mut map = FunctionRegistry::default(); diff --git a/src/expr/core/src/sig/udf.rs b/src/expr/core/src/sig/udf.rs new file mode 100644 index 0000000000000..9a253a78051e9 --- /dev/null +++ b/src/expr/core/src/sig/udf.rs @@ -0,0 +1,165 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +//! UDF implementation interface. +//! +//! To support a new language or runtime for UDF, implement the interface in this module. +//! +//! See expr/impl/src/udf for the implementations. + +use anyhow::{bail, Context, Result}; +use arrow_array::{ArrayRef, BooleanArray, RecordBatch}; +use enum_as_inner::EnumAsInner; +use futures::stream::BoxStream; +use risingwave_common::types::DataType; + +/// The global registry of UDF implementations. +/// +/// To register a new UDF implementation: +/// +/// ```ignore +/// #[linkme::distributed_slice(UDF_IMPLS)] +/// static MY_UDF_LANGUAGE: UdfImplDescriptor = UdfImplDescriptor {...}; +/// ``` +#[linkme::distributed_slice] +pub static UDF_IMPLS: [UdfImplDescriptor]; + +/// Find a UDF implementation by language. +pub fn find_udf_impl( + language: &str, + runtime: Option<&str>, + link: Option<&str>, +) -> Result<&'static UdfImplDescriptor> { + let mut impls = UDF_IMPLS + .iter() + .filter(|desc| (desc.match_fn)(language, runtime, link)); + let impl_ = impls.next().context( + "language not found.\nHINT: UDF feature flag may not be enabled during compilation", + )?; + if impls.next().is_some() { + bail!("multiple UDF implementations found for language: {language}"); + } + Ok(impl_) +} + +/// UDF implementation descriptor. +/// +/// Every UDF implementation should provide 3 functions: +pub struct UdfImplDescriptor { + /// Returns if a function matches the implementation. + /// + /// This function is used to determine which implementation to use for a UDF. + pub match_fn: fn(language: &str, runtime: Option<&str>, link: Option<&str>) -> bool, + + /// Creates a function from options. + /// + /// This function will be called when `create function` statement is executed on the frontend. + pub create_fn: fn(opts: CreateFunctionOptions<'_>) -> Result, + + /// Builds UDF runtime from verified options. + /// + /// This function will be called before the UDF is executed on the backend. + pub build_fn: fn(opts: UdfOptions<'_>) -> Result>, +} + +/// Options for creating a function. +/// +/// These information are parsed from `CREATE FUNCTION` statement. +/// Implementations should verify the options and return a `CreateFunctionOutput` in `create_fn`. +pub struct CreateFunctionOptions<'a> { + pub kind: UdfKind, + pub name: &'a str, + pub arg_names: &'a [String], + pub arg_types: &'a [DataType], + pub return_type: &'a DataType, + pub as_: Option<&'a str>, + pub using_link: Option<&'a str>, + pub using_base64_decoded: Option<&'a [u8]>, +} + +/// Output of creating a function. +pub struct CreateFunctionOutput { + pub identifier: String, + pub body: Option, + pub compressed_binary: Option>, +} + +/// Options for building a UDF runtime. +pub struct UdfOptions<'a> { + pub kind: UdfKind, + pub body: Option<&'a str>, + pub compressed_binary: Option<&'a [u8]>, + pub link: Option<&'a str>, + pub identifier: &'a str, + pub arg_names: &'a [String], + pub return_type: &'a DataType, + pub always_retry_on_network_error: bool, + pub function_type: Option<&'a str>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumAsInner)] +pub enum UdfKind { + Scalar, + Table, + Aggregate, +} + +/// UDF implementation. +#[async_trait::async_trait] +pub trait UdfImpl: std::fmt::Debug + Send + Sync { + /// Call the scalar function. + async fn call(&self, input: &RecordBatch) -> Result; + + /// Call the table function. + async fn call_table_function<'a>( + &'a self, + input: &'a RecordBatch, + ) -> Result>>; + + /// For aggregate function, create the initial state. + fn call_agg_create_state(&self) -> Result { + bail!("aggregate function is not supported"); + } + + /// For aggregate function, accumulate or retract the state. + fn call_agg_accumulate_or_retract( + &self, + _state: &ArrayRef, + _ops: &BooleanArray, + _input: &RecordBatch, + ) -> Result { + bail!("aggregate function is not supported"); + } + + /// For aggregate function, get aggregate result from the state. + fn call_agg_finish(&self, _state: &ArrayRef) -> Result { + bail!("aggregate function is not supported"); + } + + /// Whether the UDF talks in legacy mode. + /// + /// If true, decimal and jsonb types are mapped to Arrow `LargeBinary` and `LargeUtf8` types. + /// Otherwise, they are mapped to Arrow extension types. + /// See . + fn is_legacy(&self) -> bool { + false + } + + /// Return the memory size consumed by UDF runtime in bytes. + /// + /// If not available, return 0. + fn memory_usage(&self) -> usize { + 0 + } +} diff --git a/src/expr/core/src/table_function/mod.rs b/src/expr/core/src/table_function/mod.rs index 9d2d747cc4ed5..d2d8e291ee076 100644 --- a/src/expr/core/src/table_function/mod.rs +++ b/src/expr/core/src/table_function/mod.rs @@ -12,16 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use either::Either; use futures_async_stream::try_stream; use futures_util::stream::BoxStream; use futures_util::StreamExt; -use itertools::Itertools; -use risingwave_common::array::{Array, ArrayBuilder, ArrayImpl, ArrayRef, DataChunk}; +use risingwave_common::array::{Array, ArrayBuilder, ArrayImpl, DataChunk}; use risingwave_common::types::{DataType, DatumRef}; -use risingwave_pb::expr::project_set_select_item::SelectItem; use risingwave_pb::expr::table_function::PbType; -use risingwave_pb::expr::{PbProjectSetSelectItem, PbTableFunction}; +use risingwave_pb::expr::PbTableFunction; use super::{ExprError, Result}; use crate::expr::{build_from_prost as expr_build_from_prost, BoxedExpression}; @@ -34,20 +31,21 @@ pub use self::empty::*; pub use self::repeat::*; use self::user_defined::*; -/// Instance of a table function. +/// A table function takes a row as input and returns multiple rows as output. /// -/// A table function takes a row as input and returns a table. It is also known as Set-Returning -/// Function. +/// It is also known as Set-Returning Function. #[async_trait::async_trait] pub trait TableFunction: std::fmt::Debug + Sync + Send { + /// The data type of the output. fn return_type(&self) -> DataType; /// # Contract of the output /// - /// The returned `DataChunk` contains exact two columns: + /// The returned `DataChunk` contains two or three columns: /// - The first column is an I32Array containing row indices of input chunk. It should be /// monotonically increasing. /// - The second column is the output values. The data type of the column is `return_type`. + /// - (Optional) If any error occurs, the error message is stored in the third column. /// /// i.e., for the `i`-th input row, the output rows are `(i, output_1)`, `(i, output_2)`, ... /// @@ -108,9 +106,7 @@ pub trait TableFunction: std::fmt::Debug + Sync + Send { pub type BoxedTableFunction = Box; pub fn build_from_prost(prost: &PbTableFunction, chunk_size: usize) -> Result { - use risingwave_pb::expr::table_function::Type::*; - - if prost.get_function_type().unwrap() == Udtf { + if prost.get_function_type().unwrap() == PbType::UserDefined { return new_user_defined(prost, chunk_size); } @@ -129,6 +125,7 @@ pub fn build( chunk_size: usize, children: Vec, ) -> Result { + use itertools::Itertools; let args = children.iter().map(|t| t.return_type()).collect_vec(); let desc = crate::sig::FUNCTION_REGISTRY .get(func, &args, &return_type) @@ -143,53 +140,6 @@ pub fn build( desc.build_table(return_type, chunk_size, children) } -/// See also [`PbProjectSetSelectItem`] -#[derive(Debug)] -pub enum ProjectSetSelectItem { - TableFunction(BoxedTableFunction), - Expr(BoxedExpression), -} - -impl From for ProjectSetSelectItem { - fn from(table_function: BoxedTableFunction) -> Self { - ProjectSetSelectItem::TableFunction(table_function) - } -} - -impl From for ProjectSetSelectItem { - fn from(expr: BoxedExpression) -> Self { - ProjectSetSelectItem::Expr(expr) - } -} - -impl ProjectSetSelectItem { - pub fn from_prost(prost: &PbProjectSetSelectItem, chunk_size: usize) -> Result { - match prost.select_item.as_ref().unwrap() { - SelectItem::Expr(expr) => expr_build_from_prost(expr).map(Into::into), - SelectItem::TableFunction(tf) => build_from_prost(tf, chunk_size).map(Into::into), - } - } - - pub fn return_type(&self) -> DataType { - match self { - ProjectSetSelectItem::TableFunction(tf) => tf.return_type(), - ProjectSetSelectItem::Expr(expr) => expr.return_type(), - } - } - - pub async fn eval<'a>( - &'a self, - input: &'a DataChunk, - ) -> Result, ArrayRef>> { - match self { - Self::TableFunction(tf) => Ok(Either::Left( - TableFunctionOutputIter::new(tf.eval(input).await).await?, - )), - Self::Expr(expr) => expr.eval(input).await.map(Either::Right), - } - } -} - /// A wrapper over the output of table function that allows iteration by rows. /// /// If the table function returns multiple columns, the output will be struct values. @@ -224,7 +174,7 @@ impl ProjectSetSelectItem { /// for i in 0..4 { /// let (index, value) = iter.peek().unwrap(); /// assert_eq!(index, i); -/// assert_eq!(value, Some((i as i64).into())); +/// assert_eq!(value.unwrap(), Some((i as i64).into())); /// iter.next().await.unwrap(); /// } /// assert!(iter.peek().is_none()); @@ -250,11 +200,19 @@ impl<'a> TableFunctionOutputIter<'a> { } /// Gets the current row. - pub fn peek(&'a self) -> Option<(usize, DatumRef<'a>)> { + pub fn peek(&'a self) -> Option<(usize, Result>)> { let chunk = self.chunk.as_ref()?; let index = chunk.column_at(0).as_int32().value_at(self.index).unwrap() as usize; - let value = chunk.column_at(1).value_at(self.index); - Some((index, value)) + let result = if let Some(msg) = chunk + .columns() + .get(2) + .and_then(|errors| errors.as_utf8().value_at(self.index)) + { + Err(ExprError::Custom(msg.into())) + } else { + Ok(chunk.column_at(1).value_at(self.index)) + }; + Some((index, result)) } /// Moves to the next row. @@ -280,3 +238,20 @@ impl<'a> TableFunctionOutputIter<'a> { Ok(()) } } + +/// Checks if the output chunk returned by `TableFunction::eval` contains any error. +pub fn check_error(chunk: &DataChunk) -> Result<()> { + if let Some(errors) = chunk.columns().get(2) { + if errors.null_bitmap().any() { + return Err(ExprError::Custom( + errors + .as_utf8() + .iter() + .find_map(|s| s) + .expect("no error message") + .into(), + )); + } + } + Ok(()) +} diff --git a/src/expr/core/src/table_function/user_defined.rs b/src/expr/core/src/table_function/user_defined.rs index b65ee5e77758b..919c258299cde 100644 --- a/src/expr/core/src/table_function/user_defined.rs +++ b/src/expr/core/src/table_function/user_defined.rs @@ -15,30 +15,21 @@ use std::sync::Arc; use anyhow::Context; -use arrow_array::RecordBatch; -use arrow_schema::{Field, Fields, Schema, SchemaRef}; -use arrow_udf_js::{CallMode as JsCallMode, Runtime as JsRuntime}; -#[cfg(feature = "embedded-deno-udf")] -use arrow_udf_js_deno::{CallMode as DenoCallMode, Runtime as DenoRuntime}; -#[cfg(feature = "embedded-python-udf")] -use arrow_udf_python::{CallMode as PythonCallMode, Runtime as PythonRuntime}; -use cfg_or_panic::cfg_or_panic; -use futures_util::stream; -use risingwave_common::array::{ArrayError, DataChunk, I32Array}; +use arrow_schema::{Fields, Schema, SchemaRef}; +use risingwave_common::array::arrow::{FromArrow, ToArrow, UdfArrowConvert}; +use risingwave_common::array::I32Array; use risingwave_common::bail; -use thiserror_ext::AsReport; use super::*; -use crate::expr::expr_udf::UdfImpl; +use crate::sig::{UdfImpl, UdfKind, UdfOptions}; #[derive(Debug)] pub struct UserDefinedTableFunction { children: Vec, - #[allow(dead_code)] arg_schema: SchemaRef, return_type: DataType, - client: UdfImpl, - identifier: String, + runtime: Box, + arrow_convert: UdfArrowConvert, #[allow(dead_code)] chunk_size: usize, } @@ -49,54 +40,11 @@ impl TableFunction for UserDefinedTableFunction { self.return_type.clone() } - #[cfg_or_panic(not(madsim))] async fn eval<'a>(&'a self, input: &'a DataChunk) -> BoxStream<'a, Result> { self.eval_inner(input) } } -#[cfg(not(madsim))] -impl UdfImpl { - #[try_stream(ok = RecordBatch, error = ExprError)] - async fn call_table_function<'a>(&'a self, identifier: &'a str, input: RecordBatch) { - match self { - UdfImpl::External(client) => { - #[for_await] - for res in client - .call_stream(identifier, stream::once(async { input })) - .await? - { - yield res?; - } - } - UdfImpl::JavaScript(runtime) => { - for res in runtime.call_table_function(identifier, &input, 1024)? { - yield res?; - } - } - #[cfg(feature = "embedded-python-udf")] - UdfImpl::Python(runtime) => { - for res in runtime.call_table_function(identifier, &input, 1024)? { - yield res?; - } - } - #[cfg(feature = "embedded-deno-udf")] - UdfImpl::Deno(runtime) => { - let mut iter = runtime.call_table_function(identifier, input, 1024).await?; - while let Some(res) = iter.next().await { - yield res?; - } - } - UdfImpl::Wasm(runtime) => { - for res in runtime.call_table_function(identifier, &input)? { - yield res?; - } - } - } - } -} - -#[cfg(not(madsim))] impl UserDefinedTableFunction { #[try_stream(boxed, ok = DataChunk, error = ExprError)] async fn eval_inner<'a>(&'a self, input: &'a DataChunk) { @@ -109,17 +57,16 @@ impl UserDefinedTableFunction { let direct_input = DataChunk::new(columns, input.visibility().clone()); // compact the input chunk and record the row mapping - let visible_rows = direct_input.visibility().iter_ones().collect_vec(); - let compacted_input = direct_input.compact_cow(); - let arrow_input = RecordBatch::try_from(compacted_input.as_ref())?; + let visible_rows = direct_input.visibility().iter_ones().collect::>(); + // this will drop invisible rows + let arrow_input = self + .arrow_convert + .to_record_batch(self.arg_schema.clone(), &direct_input)?; // call UDTF #[for_await] - for res in self - .client - .call_table_function(&self.identifier, arrow_input) - { - let output = DataChunk::try_from(&res?)?; + for res in self.runtime.call_table_function(&arrow_input).await? { + let output = self.arrow_convert.from_record_batch(&res?)?; self.check_output(&output)?; // we send the compacted input to UDF, so we need to map the row indices back to the @@ -173,126 +120,46 @@ impl UserDefinedTableFunction { } } -#[cfg_or_panic(not(madsim))] pub fn new_user_defined(prost: &PbTableFunction, chunk_size: usize) -> Result { - let Some(udtf) = &prost.udtf else { - bail!("expect UDTF"); - }; + let udf = prost.get_udf()?; - let arg_schema = Arc::new(Schema::new( - udtf.arg_types - .iter() - .map::, _>(|t| { - Ok(Field::new( - "", - DataType::from(t).try_into().map_err(|e: ArrayError| { - risingwave_udf::Error::unsupported(e.to_report_string()) - })?, - true, - )) - }) - .try_collect::<_, Fields, _>()?, - )); - - let identifier = udtf.get_identifier()?; + let identifier = udf.get_identifier()?; let return_type = DataType::from(prost.get_return_type()?); - #[cfg(not(feature = "embedded-deno-udf"))] - let runtime = "quickjs"; - - #[cfg(feature = "embedded-deno-udf")] - let runtime = match udtf.runtime.as_deref() { - Some("deno") => "deno", - _ => "quickjs", - }; - - let client = match udtf.language.as_str() { - "wasm" | "rust" => { - let compressed_wasm_binary = udtf.get_compressed_binary()?; - let wasm_binary = zstd::stream::decode_all(compressed_wasm_binary.as_slice()) - .context("failed to decompress wasm binary")?; - let runtime = crate::expr::expr_udf::get_or_create_wasm_runtime(&wasm_binary)?; - UdfImpl::Wasm(runtime) - } - "javascript" if runtime != "deno" => { - let mut rt = JsRuntime::new()?; - let body = format!( - "export function* {}({}) {{ {} }}", - identifier, - udtf.arg_names.join(","), - udtf.get_body()? - ); - rt.add_function( - identifier, - arrow_schema::DataType::try_from(&return_type)?, - JsCallMode::CalledOnNullInput, - &body, - )?; - UdfImpl::JavaScript(rt) - } - #[cfg(feature = "embedded-deno-udf")] - "javascript" if runtime == "deno" => { - let rt = DenoRuntime::new(); - let body = match udtf.get_body() { - Ok(body) => body.clone(), - Err(_) => match udtf.get_compressed_binary() { - Ok(compressed_binary) => { - let binary = zstd::stream::decode_all(compressed_binary.as_slice()) - .context("failed to decompress binary")?; - String::from_utf8(binary).context("failed to decode binary")? - } - Err(_) => { - bail!("UDF body or compressed binary is required for deno UDF"); - } - }, - }; - - let body = format!( - "export {} {}({}) {{ {} }}", - match udtf.function_type.as_deref() { - Some("async") => "async function", - Some("async_generator") => "async function*", - Some("sync") => "function", - _ => "function*", - }, - identifier, - udtf.arg_names.join(","), - body - ); - - futures::executor::block_on(rt.add_function( - identifier, - arrow_schema::DataType::try_from(&return_type)?, - DenoCallMode::CalledOnNullInput, - &body, - ))?; - UdfImpl::Deno(rt) - } - #[cfg(feature = "embedded-python-udf")] - "python" if udtf.body.is_some() => { - let mut rt = PythonRuntime::builder().sandboxed(true).build()?; - let body = udtf.get_body()?; - rt.add_function( - identifier, - arrow_schema::DataType::try_from(&return_type)?, - PythonCallMode::CalledOnNullInput, - body, - )?; - UdfImpl::Python(rt) - } - // connect to UDF service - _ => { - let link = udtf.get_link()?; - UdfImpl::External(crate::expr::expr_udf::get_or_create_flight_client(link)?) - } + let language = udf.language.as_str(); + let runtime = udf.runtime.as_deref(); + let link = udf.link.as_deref(); + + let build_fn = crate::sig::find_udf_impl(language, runtime, link)?.build_fn; + let runtime = build_fn(UdfOptions { + kind: UdfKind::Table, + body: udf.body.as_deref(), + compressed_binary: udf.compressed_binary.as_deref(), + link: udf.link.as_deref(), + identifier, + arg_names: &udf.arg_names, + return_type: &return_type, + always_retry_on_network_error: false, + function_type: udf.function_type.as_deref(), + }) + .context("failed to build UDF runtime")?; + + let arrow_convert = UdfArrowConvert { + legacy: runtime.is_legacy(), }; + let arg_schema = Arc::new(Schema::new( + udf.arg_types + .iter() + .map(|t| arrow_convert.to_arrow_field("", &DataType::from(t))) + .try_collect::()?, + )); Ok(UserDefinedTableFunction { children: prost.args.iter().map(expr_build_from_prost).try_collect()?, return_type, arg_schema, - client, - identifier: identifier.clone(), + runtime, + arrow_convert, chunk_size, } .boxed()) diff --git a/src/expr/core/src/window_function/call.rs b/src/expr/core/src/window_function/call.rs index f5714c64345a1..5c6f40ff25bc0 100644 --- a/src/expr/core/src/window_function/call.rs +++ b/src/expr/core/src/window_function/call.rs @@ -13,34 +13,19 @@ // limitations under the License. use std::fmt::Display; -use std::ops::Deref; -use std::sync::Arc; -use anyhow::Context; -use educe::Educe; use enum_as_inner::EnumAsInner; -use futures_util::FutureExt; use parse_display::Display; -use risingwave_common::row::OwnedRow; -use risingwave_common::types::{ - DataType, Datum, IsNegative, ScalarImpl, ScalarRefImpl, Sentinelled, ToOwnedDatum, ToText, -}; -use risingwave_common::util::sort_util::{Direction, OrderType}; -use risingwave_common::util::value_encoding::{DatumFromProtoExt, DatumToProtoExt}; +use risingwave_common::types::DataType; use risingwave_common::{bail, must_match}; -use risingwave_pb::expr::window_frame::{ - PbBound, PbBoundType, PbBounds, PbExclusion, PbRangeFrameBound, PbRangeFrameBounds, - PbRowsFrameBound, PbRowsFrameBounds, -}; +use risingwave_pb::expr::window_frame::{PbBounds, PbExclusion}; use risingwave_pb::expr::{PbWindowFrame, PbWindowFunction}; use FrameBound::{CurrentRow, Following, Preceding, UnboundedFollowing, UnboundedPreceding}; -use super::WindowFuncKind; -use crate::aggregate::AggArgs; -use crate::expr::{ - build_func, BoxedExpression, Expression, ExpressionBoxExt, InputRefExpression, - LiteralExpression, +use super::{ + RangeFrameBounds, RowsFrameBound, RowsFrameBounds, SessionFrameBounds, WindowFuncKind, }; +use crate::aggregate::AggArgs; use crate::Result; #[derive(Debug, Clone)] @@ -117,6 +102,10 @@ impl Frame { let bounds = must_match!(frame.get_bounds()?, PbBounds::Range(bounds) => bounds); FrameBounds::Range(RangeFrameBounds::from_protobuf(bounds)?) } + PbType::Session => { + let bounds = must_match!(frame.get_bounds()?, PbBounds::Session(bounds) => bounds); + FrameBounds::Session(SessionFrameBounds::from_protobuf(bounds)?) + } }; let exclusion = FrameExclusion::from_protobuf(frame.get_exclusion()?)?; Ok(Self { bounds, exclusion }) @@ -125,8 +114,8 @@ impl Frame { pub fn to_protobuf(&self) -> PbWindowFrame { use risingwave_pb::expr::window_frame::PbType; let exclusion = self.exclusion.to_protobuf() as _; + #[expect(deprecated)] // because of `start` and `end` fields match &self.bounds { - #[expect(deprecated)] FrameBounds::Rows(bounds) => PbWindowFrame { r#type: PbType::Rows as _, start: None, // deprecated @@ -134,7 +123,6 @@ impl Frame { exclusion, bounds: Some(PbBounds::Rows(bounds.to_protobuf())), }, - #[expect(deprecated)] FrameBounds::Range(bounds) => PbWindowFrame { r#type: PbType::Range as _, start: None, // deprecated @@ -142,6 +130,13 @@ impl Frame { exclusion, bounds: Some(PbBounds::Range(bounds.to_protobuf())), }, + FrameBounds::Session(bounds) => PbWindowFrame { + r#type: PbType::Session as _, + start: None, // deprecated + end: None, // deprecated + exclusion, + bounds: Some(PbBounds::Session(bounds.to_protobuf())), + }, } } } @@ -152,6 +147,7 @@ pub enum FrameBounds { Rows(RowsFrameBounds), // Groups(GroupsFrameBounds), Range(RangeFrameBounds), + Session(SessionFrameBounds), } impl FrameBounds { @@ -159,6 +155,7 @@ impl FrameBounds { match self { Self::Rows(bounds) => bounds.validate(), Self::Range(bounds) => bounds.validate(), + Self::Session(bounds) => bounds.validate(), } } @@ -166,6 +163,7 @@ impl FrameBounds { match self { Self::Rows(RowsFrameBounds { start, .. }) => start.is_unbounded_preceding(), Self::Range(RangeFrameBounds { start, .. }) => start.is_unbounded_preceding(), + Self::Session(_) => false, } } @@ -173,6 +171,7 @@ impl FrameBounds { match self { Self::Rows(RowsFrameBounds { end, .. }) => end.is_unbounded_following(), Self::Range(RangeFrameBounds { end, .. }) => end.is_unbounded_following(), + Self::Session(_) => false, } } @@ -185,324 +184,6 @@ pub trait FrameBoundsImpl { fn validate(&self) -> Result<()>; } -#[derive(Display, Debug, Clone, Eq, PartialEq, Hash)] -#[display("ROWS BETWEEN {start} AND {end}")] -pub struct RowsFrameBounds { - pub start: RowsFrameBound, - pub end: RowsFrameBound, -} - -impl RowsFrameBounds { - fn from_protobuf(bounds: &PbRowsFrameBounds) -> Result { - let start = FrameBound::::from_protobuf(bounds.get_start()?)?; - let end = FrameBound::::from_protobuf(bounds.get_end()?)?; - Ok(Self { start, end }) - } - - fn to_protobuf(&self) -> PbRowsFrameBounds { - PbRowsFrameBounds { - start: Some(self.start.to_protobuf()), - end: Some(self.end.to_protobuf()), - } - } -} - -impl RowsFrameBounds { - /// Check if the `ROWS` frame is canonical. - /// - /// A canonical `ROWS` frame is defined as: - /// - /// - Its bounds are valid (see [`Self::validate`]). - /// - It contains the current row. - pub fn is_canonical(&self) -> bool { - self.validate().is_ok() && { - let start = self.start.to_offset(); - let end = self.end.to_offset(); - start.unwrap_or(0) <= 0 && end.unwrap_or(0) >= 0 - } - } - - /// Get the number of preceding rows. - pub fn n_preceding_rows(&self) -> Option { - match (&self.start, &self.end) { - (UnboundedPreceding, _) => None, - (Preceding(n1), Preceding(n2)) => Some(*n1.max(n2)), - (Preceding(n), _) => Some(*n), - (CurrentRow | Following(_) | UnboundedFollowing, _) => Some(0), - } - } - - /// Get the number of following rows. - pub fn n_following_rows(&self) -> Option { - match (&self.start, &self.end) { - (_, UnboundedFollowing) => None, - (Following(n1), Following(n2)) => Some(*n1.max(n2)), - (_, Following(n)) => Some(*n), - (_, CurrentRow | Preceding(_) | UnboundedPreceding) => Some(0), - } - } -} - -impl FrameBoundsImpl for RowsFrameBounds { - fn validate(&self) -> Result<()> { - FrameBound::validate_bounds(&self.start, &self.end, |_| Ok(())) - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct RangeFrameBounds { - pub order_data_type: DataType, - pub order_type: OrderType, - pub offset_data_type: DataType, - pub start: RangeFrameBound, - pub end: RangeFrameBound, -} - -impl RangeFrameBounds { - fn from_protobuf(bounds: &PbRangeFrameBounds) -> Result { - let order_data_type = DataType::from(bounds.get_order_data_type()?); - let order_type = OrderType::from_protobuf(bounds.get_order_type()?); - let offset_data_type = DataType::from(bounds.get_offset_data_type()?); - let start = FrameBound::::from_protobuf( - bounds.get_start()?, - &order_data_type, - &offset_data_type, - )?; - let end = FrameBound::::from_protobuf( - bounds.get_end()?, - &order_data_type, - &offset_data_type, - )?; - Ok(Self { - order_data_type, - order_type, - offset_data_type, - start, - end, - }) - } - - fn to_protobuf(&self) -> PbRangeFrameBounds { - PbRangeFrameBounds { - start: Some(self.start.to_protobuf()), - end: Some(self.end.to_protobuf()), - order_data_type: Some(self.order_data_type.to_protobuf()), - order_type: Some(self.order_type.to_protobuf()), - offset_data_type: Some(self.offset_data_type.to_protobuf()), - } - } -} - -/// The wrapper type for [`ScalarImpl`] range frame offset, containing -/// two expressions to help adding and subtracting the offset. -#[derive(Debug, Clone, Educe)] -#[educe(PartialEq, Eq, Hash)] -pub struct RangeFrameOffset { - /// The original offset value. - offset: ScalarImpl, - /// Built expression for `$0 + offset`. - #[educe(PartialEq(ignore), Hash(ignore))] - add_expr: Option>, - /// Built expression for `$0 - offset`. - #[educe(PartialEq(ignore), Hash(ignore))] - sub_expr: Option>, -} - -impl RangeFrameOffset { - pub fn new(offset: ScalarImpl) -> Self { - Self { - offset, - add_expr: None, - sub_expr: None, - } - } - - fn build_exprs( - &mut self, - order_data_type: &DataType, - offset_data_type: &DataType, - ) -> Result<()> { - use risingwave_pb::expr::expr_node::PbType as PbExprType; - - let input_expr = InputRefExpression::new(order_data_type.clone(), 0); - let offset_expr = - LiteralExpression::new(offset_data_type.clone(), Some(self.offset.clone())); - self.add_expr = Some(Arc::new(build_func( - PbExprType::Add, - order_data_type.clone(), - vec![input_expr.clone().boxed(), offset_expr.clone().boxed()], - )?)); - self.sub_expr = Some(Arc::new(build_func( - PbExprType::Subtract, - order_data_type.clone(), - vec![input_expr.boxed(), offset_expr.boxed()], - )?)); - Ok(()) - } - - pub fn new_for_test( - offset: ScalarImpl, - order_data_type: &DataType, - offset_data_type: &DataType, - ) -> Self { - let mut offset = Self::new(offset); - offset - .build_exprs(order_data_type, offset_data_type) - .unwrap(); - offset - } -} - -impl Deref for RangeFrameOffset { - type Target = ScalarImpl; - - fn deref(&self) -> &Self::Target { - &self.offset - } -} - -impl Display for RangeFrameBounds { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "RANGE BETWEEN {} AND {}", - self.start.for_display(), - self.end.for_display() - )?; - Ok(()) - } -} - -impl FrameBoundsImpl for RangeFrameBounds { - fn validate(&self) -> Result<()> { - fn validate_non_negative(val: impl IsNegative + Display) -> Result<()> { - if val.is_negative() { - bail!( - "frame bound offset should be non-negative, but {} is given", - val - ); - } - Ok(()) - } - - FrameBound::validate_bounds(&self.start, &self.end, |offset| { - match offset.as_scalar_ref_impl() { - // TODO(rc): use decl macro? - ScalarRefImpl::Int16(val) => validate_non_negative(val)?, - ScalarRefImpl::Int32(val) => validate_non_negative(val)?, - ScalarRefImpl::Int64(val) => validate_non_negative(val)?, - ScalarRefImpl::Float32(val) => validate_non_negative(val)?, - ScalarRefImpl::Float64(val) => validate_non_negative(val)?, - ScalarRefImpl::Decimal(val) => validate_non_negative(val)?, - ScalarRefImpl::Interval(val) => { - if !val.is_never_negative() { - bail!( - "for frame bound offset of type `interval`, each field should be non-negative, but {} is given", - val - ); - } - if matches!(self.order_data_type, DataType::Timestamptz) { - // for `timestamptz`, we only support offset without `month` and `day` fields - if val.months() != 0 || val.days() != 0 { - bail!( - "for frame order column of type `timestamptz`, offset should not have non-zero `month` and `day`", - ); - } - } - }, - _ => unreachable!("other order column data types are not supported and should be banned in frontend"), - } - Ok(()) - }) - } -} - -impl RangeFrameBounds { - /// Get the frame start for a given order column value. - /// - /// ## Examples - /// - /// For the following frames: - /// - /// ```sql - /// ORDER BY x ASC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - /// ORDER BY x DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - /// ``` - /// - /// For any CURRENT ROW with any order value, the frame start is always the first-most row, which is - /// represented by [`Sentinelled::Smallest`]. - /// - /// For the following frame: - /// - /// ```sql - /// ORDER BY x ASC RANGE BETWEEN 10 PRECEDING AND CURRENT ROW - /// ``` - /// - /// For CURRENT ROW with order value `100`, the frame start is the **FIRST** row with order value `90`. - /// - /// For the following frame: - /// - /// ```sql - /// ORDER BY x DESC RANGE BETWEEN 10 PRECEDING AND CURRENT ROW - /// ``` - /// - /// For CURRENT ROW with order value `100`, the frame start is the **FIRST** row with order value `110`. - pub fn frame_start_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { - self.start.for_calc().bound_of(order_value, self.order_type) - } - - /// Get the frame end for a given order column value. It's very similar to `frame_start_of`, just with - /// everything on the other direction. - pub fn frame_end_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { - self.end.for_calc().bound_of(order_value, self.order_type) - } - - /// Get the order value of the CURRENT ROW of the first frame that includes the given order value. - /// - /// ## Examples - /// - /// For the following frames: - /// - /// ```sql - /// ORDER BY x ASC RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING - /// ORDER BY x DESC RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING - /// ``` - /// - /// For any given order value, the first CURRENT ROW is always the first-most row, which is - /// represented by [`Sentinelled::Smallest`]. - /// - /// For the following frame: - /// - /// ```sql - /// ORDER BY x ASC RANGE BETWEEN CURRENT ROW AND 10 FOLLOWING - /// ``` - /// - /// For a given order value `100`, the first CURRENT ROW should have order value `90`. - /// - /// For the following frame: - /// - /// ```sql - /// ORDER BY x DESC RANGE BETWEEN CURRENT ROW AND 10 FOLLOWING - /// ``` - /// - /// For a given order value `100`, the first CURRENT ROW should have order value `110`. - pub fn first_curr_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { - self.end - .for_calc() - .reverse() - .bound_of(order_value, self.order_type) - } - - /// Get the order value of the CURRENT ROW of the last frame that includes the given order value. - /// It's very similar to `first_curr_of`, just with everything on the other direction. - pub fn last_curr_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { - self.start - .for_calc() - .reverse() - .bound_of(order_value, self.order_type) - } -} - #[derive(Display, Debug, Clone, Eq, PartialEq, Hash, EnumAsInner)] #[display(style = "TITLE CASE")] pub enum FrameBound { @@ -515,9 +196,6 @@ pub enum FrameBound { UnboundedFollowing, } -pub type RowsFrameBound = FrameBound; -pub type RangeFrameBound = FrameBound; - impl FrameBound { fn offset_value(&self) -> Option<&T> { match self { @@ -526,7 +204,7 @@ impl FrameBound { } } - fn validate_bounds( + pub(super) fn validate_bounds( start: &Self, end: &Self, offset_checker: impl Fn(&T) -> Result<()>, @@ -569,7 +247,7 @@ impl FrameBound where T: Copy, { - fn reverse(self) -> FrameBound { + pub(super) fn reverse(self) -> FrameBound { match self { UnboundedPreceding => UnboundedFollowing, Preceding(offset) => Following(offset), @@ -580,172 +258,6 @@ where } } -impl RowsFrameBound { - fn from_protobuf_legacy(bound: &PbBound) -> Result { - use risingwave_pb::expr::window_frame::bound::PbOffset; - - let offset = bound.get_offset()?; - let bound = match offset { - PbOffset::Integer(offset) => Self::from_protobuf(&PbRowsFrameBound { - r#type: bound.get_type()? as _, - offset: Some(*offset), - })?, - PbOffset::Datum(_) => bail!("offset of `RowsFrameBound` must be `Integer`"), - }; - Ok(bound) - } - - fn from_protobuf(bound: &PbRowsFrameBound) -> Result { - let bound = match bound.get_type()? { - PbBoundType::Unspecified => bail!("unspecified type of `RowsFrameBound`"), - PbBoundType::UnboundedPreceding => Self::UnboundedPreceding, - PbBoundType::Preceding => Self::Preceding(*bound.get_offset()? as usize), - PbBoundType::CurrentRow => Self::CurrentRow, - PbBoundType::Following => Self::Following(*bound.get_offset()? as usize), - PbBoundType::UnboundedFollowing => Self::UnboundedFollowing, - }; - Ok(bound) - } - - fn to_protobuf(&self) -> PbRowsFrameBound { - let (r#type, offset) = match self { - Self::UnboundedPreceding => (PbBoundType::UnboundedPreceding, None), - Self::Preceding(offset) => (PbBoundType::Preceding, Some(*offset as _)), - Self::CurrentRow => (PbBoundType::CurrentRow, None), - Self::Following(offset) => (PbBoundType::Following, Some(*offset as _)), - Self::UnboundedFollowing => (PbBoundType::UnboundedFollowing, None), - }; - PbRowsFrameBound { - r#type: r#type as _, - offset, - } - } -} - -impl RowsFrameBound { - /// Convert the bound to sized offset from current row. `None` if the bound is unbounded. - pub fn to_offset(&self) -> Option { - match self { - UnboundedPreceding | UnboundedFollowing => None, - CurrentRow => Some(0), - Preceding(n) => Some(-(*n as isize)), - Following(n) => Some(*n as isize), - } - } -} - -impl RangeFrameBound { - fn from_protobuf( - bound: &PbRangeFrameBound, - order_data_type: &DataType, - offset_data_type: &DataType, - ) -> Result { - let bound = match bound.get_type()? { - PbBoundType::Unspecified => bail!("unspecified type of `RangeFrameBound`"), - PbBoundType::UnboundedPreceding => Self::UnboundedPreceding, - PbBoundType::CurrentRow => Self::CurrentRow, - PbBoundType::UnboundedFollowing => Self::UnboundedFollowing, - bound_type @ (PbBoundType::Preceding | PbBoundType::Following) => { - let offset_value = Datum::from_protobuf(bound.get_offset()?, offset_data_type) - .context("offset `Datum` is not decodable")? - .context("offset of `RangeFrameBound` must be non-NULL")?; - let mut offset = RangeFrameOffset::new(offset_value); - offset.build_exprs(order_data_type, offset_data_type)?; - if bound_type == PbBoundType::Preceding { - Self::Preceding(offset) - } else { - Self::Following(offset) - } - } - }; - Ok(bound) - } - - fn to_protobuf(&self) -> PbRangeFrameBound { - let (r#type, offset) = match self { - Self::UnboundedPreceding => (PbBoundType::UnboundedPreceding, None), - Self::Preceding(offset) => ( - PbBoundType::Preceding, - Some(Some(offset.as_scalar_ref_impl()).to_protobuf()), - ), - Self::CurrentRow => (PbBoundType::CurrentRow, None), - Self::Following(offset) => ( - PbBoundType::Following, - Some(Some(offset.as_scalar_ref_impl()).to_protobuf()), - ), - Self::UnboundedFollowing => (PbBoundType::UnboundedFollowing, None), - }; - PbRangeFrameBound { - r#type: r#type as _, - offset, - } - } -} - -impl RangeFrameBound { - fn for_display(&self) -> FrameBound { - match self { - UnboundedPreceding => UnboundedPreceding, - Preceding(offset) => Preceding(offset.as_scalar_ref_impl().to_text()), - CurrentRow => CurrentRow, - Following(offset) => Following(offset.as_scalar_ref_impl().to_text()), - UnboundedFollowing => UnboundedFollowing, - } - } - - fn for_calc(&self) -> FrameBound> { - match self { - UnboundedPreceding => UnboundedPreceding, - Preceding(offset) => Preceding(RangeFrameOffsetRef { - add_expr: offset.add_expr.as_ref().unwrap().as_ref(), - sub_expr: offset.sub_expr.as_ref().unwrap().as_ref(), - }), - CurrentRow => CurrentRow, - Following(offset) => Following(RangeFrameOffsetRef { - add_expr: offset.add_expr.as_ref().unwrap().as_ref(), - sub_expr: offset.sub_expr.as_ref().unwrap().as_ref(), - }), - UnboundedFollowing => UnboundedFollowing, - } - } -} - -#[derive(Debug, Educe)] -#[educe(Clone, Copy)] -pub struct RangeFrameOffsetRef<'a> { - /// Built expression for `$0 + offset`. - add_expr: &'a dyn Expression, - /// Built expression for `$0 - offset`. - sub_expr: &'a dyn Expression, -} - -impl FrameBound> { - fn bound_of(self, order_value: impl ToOwnedDatum, order_type: OrderType) -> Sentinelled { - let expr = match (self, order_type.direction()) { - (UnboundedPreceding, _) => return Sentinelled::Smallest, - (UnboundedFollowing, _) => return Sentinelled::Largest, - (CurrentRow, _) => return Sentinelled::Normal(order_value.to_owned_datum()), - (Preceding(offset), Direction::Ascending) - | (Following(offset), Direction::Descending) => { - // should SUBTRACT the offset - offset.sub_expr - } - (Following(offset), Direction::Ascending) - | (Preceding(offset), Direction::Descending) => { - // should ADD the offset - offset.add_expr - } - }; - let row = OwnedRow::new(vec![order_value.to_owned_datum()]); - Sentinelled::Normal( - expr.eval_row(&row) - .now_or_never() - .expect("frame bound calculation should finish immediately") - .expect("just simple calculation, should succeed"), // TODO(rc): handle overflow - ) - } -} - #[derive(Display, Debug, Copy, Clone, Eq, PartialEq, Hash, Default, EnumAsInner)] #[display("EXCLUDE {}", style = "TITLE CASE")] pub enum FrameExclusion { @@ -773,92 +285,3 @@ impl FrameExclusion { } } } - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_rows_frame_bounds() { - let bounds = RowsFrameBounds { - start: Preceding(1), - end: CurrentRow, - }; - assert!(bounds.validate().is_ok()); - assert!(bounds.is_canonical()); - assert_eq!(bounds.start.to_offset(), Some(-1)); - assert_eq!(bounds.end.to_offset(), Some(0)); - assert_eq!(bounds.n_preceding_rows(), Some(1)); - assert_eq!(bounds.n_following_rows(), Some(0)); - - let bounds = RowsFrameBounds { - start: CurrentRow, - end: Following(1), - }; - assert!(bounds.validate().is_ok()); - assert!(bounds.is_canonical()); - assert_eq!(bounds.start.to_offset(), Some(0)); - assert_eq!(bounds.end.to_offset(), Some(1)); - assert_eq!(bounds.n_preceding_rows(), Some(0)); - assert_eq!(bounds.n_following_rows(), Some(1)); - - let bounds = RowsFrameBounds { - start: UnboundedPreceding, - end: Following(10), - }; - assert!(bounds.validate().is_ok()); - assert!(bounds.is_canonical()); - assert_eq!(bounds.start.to_offset(), None); - assert_eq!(bounds.end.to_offset(), Some(10)); - assert_eq!(bounds.n_preceding_rows(), None); - assert_eq!(bounds.n_following_rows(), Some(10)); - - let bounds = RowsFrameBounds { - start: Preceding(10), - end: UnboundedFollowing, - }; - assert!(bounds.validate().is_ok()); - assert!(bounds.is_canonical()); - assert_eq!(bounds.start.to_offset(), Some(-10)); - assert_eq!(bounds.end.to_offset(), None); - assert_eq!(bounds.n_preceding_rows(), Some(10)); - assert_eq!(bounds.n_following_rows(), None); - - let bounds = RowsFrameBounds { - start: Preceding(1), - end: Preceding(10), - }; - assert!(bounds.validate().is_ok()); - assert!(!bounds.is_canonical()); - assert_eq!(bounds.start.to_offset(), Some(-1)); - assert_eq!(bounds.end.to_offset(), Some(-10)); - assert_eq!(bounds.n_preceding_rows(), Some(10)); - assert_eq!(bounds.n_following_rows(), Some(0)); - - let bounds = RowsFrameBounds { - start: Following(10), - end: Following(1), - }; - assert!(bounds.validate().is_ok()); - assert!(!bounds.is_canonical()); - assert_eq!(bounds.start.to_offset(), Some(10)); - assert_eq!(bounds.end.to_offset(), Some(1)); - assert_eq!(bounds.n_preceding_rows(), Some(0)); - assert_eq!(bounds.n_following_rows(), Some(10)); - - let bounds = RowsFrameBounds { - start: UnboundedFollowing, - end: Following(10), - }; - assert!(bounds.validate().is_err()); - assert!(!bounds.is_canonical()); - - let bounds = RowsFrameBounds { - start: Preceding(10), - end: UnboundedPreceding, - }; - assert!(bounds.validate().is_err()); - assert!(!bounds.is_canonical()); - } -} diff --git a/src/expr/core/src/window_function/mod.rs b/src/expr/core/src/window_function/mod.rs index dd735d086fb12..6bbde8c8755e6 100644 --- a/src/expr/core/src/window_function/mod.rs +++ b/src/expr/core/src/window_function/mod.rs @@ -17,6 +17,12 @@ pub use kind::*; mod call; pub use call::*; +mod rows; +pub use rows::*; +mod range; +pub use range::*; +mod session; +pub use session::*; mod state; pub use state::*; diff --git a/src/expr/core/src/window_function/range.rs b/src/expr/core/src/window_function/range.rs new file mode 100644 index 0000000000000..922e247dd8369 --- /dev/null +++ b/src/expr/core/src/window_function/range.rs @@ -0,0 +1,401 @@ +// Copyright 2024 RisingWave Labs +// +// 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::Display; +use std::ops::Deref; +use std::sync::Arc; + +use anyhow::Context; +use educe::Educe; +use futures_util::FutureExt; +use risingwave_common::bail; +use risingwave_common::row::OwnedRow; +use risingwave_common::types::{ + DataType, Datum, IsNegative, ScalarImpl, ScalarRefImpl, Sentinelled, ToOwnedDatum, ToText, +}; +use risingwave_common::util::sort_util::{Direction, OrderType}; +use risingwave_common::util::value_encoding::{DatumFromProtoExt, DatumToProtoExt}; +use risingwave_pb::expr::window_frame::{PbBoundType, PbRangeFrameBound, PbRangeFrameBounds}; + +use super::FrameBound::{ + self, CurrentRow, Following, Preceding, UnboundedFollowing, UnboundedPreceding, +}; +use super::FrameBoundsImpl; +use crate::expr::{ + build_func, BoxedExpression, Expression, ExpressionBoxExt, InputRefExpression, + LiteralExpression, +}; +use crate::Result; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct RangeFrameBounds { + pub order_data_type: DataType, + pub order_type: OrderType, + pub offset_data_type: DataType, + pub start: RangeFrameBound, + pub end: RangeFrameBound, +} + +impl RangeFrameBounds { + pub(super) fn from_protobuf(bounds: &PbRangeFrameBounds) -> Result { + let order_data_type = DataType::from(bounds.get_order_data_type()?); + let order_type = OrderType::from_protobuf(bounds.get_order_type()?); + let offset_data_type = DataType::from(bounds.get_offset_data_type()?); + let start = FrameBound::::from_protobuf( + bounds.get_start()?, + &order_data_type, + &offset_data_type, + )?; + let end = FrameBound::::from_protobuf( + bounds.get_end()?, + &order_data_type, + &offset_data_type, + )?; + Ok(Self { + order_data_type, + order_type, + offset_data_type, + start, + end, + }) + } + + pub(super) fn to_protobuf(&self) -> PbRangeFrameBounds { + PbRangeFrameBounds { + start: Some(self.start.to_protobuf()), + end: Some(self.end.to_protobuf()), + order_data_type: Some(self.order_data_type.to_protobuf()), + order_type: Some(self.order_type.to_protobuf()), + offset_data_type: Some(self.offset_data_type.to_protobuf()), + } + } +} + +impl Display for RangeFrameBounds { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "RANGE BETWEEN {} AND {}", + self.start.for_display(), + self.end.for_display() + )?; + Ok(()) + } +} + +impl FrameBoundsImpl for RangeFrameBounds { + fn validate(&self) -> Result<()> { + fn validate_non_negative(val: impl IsNegative + Display) -> Result<()> { + if val.is_negative() { + bail!( + "frame bound offset should be non-negative, but {} is given", + val + ); + } + Ok(()) + } + + FrameBound::validate_bounds(&self.start, &self.end, |offset| { + match offset.as_scalar_ref_impl() { + // TODO(rc): use decl macro? + ScalarRefImpl::Int16(val) => validate_non_negative(val)?, + ScalarRefImpl::Int32(val) => validate_non_negative(val)?, + ScalarRefImpl::Int64(val) => validate_non_negative(val)?, + ScalarRefImpl::Float32(val) => validate_non_negative(val)?, + ScalarRefImpl::Float64(val) => validate_non_negative(val)?, + ScalarRefImpl::Decimal(val) => validate_non_negative(val)?, + ScalarRefImpl::Interval(val) => { + if !val.is_never_negative() { + bail!( + "for frame bound offset of type `interval`, each field should be non-negative, but {} is given", + val + ); + } + if matches!(self.order_data_type, DataType::Timestamptz) { + // for `timestamptz`, we only support offset without `month` and `day` fields + if val.months() != 0 || val.days() != 0 { + bail!( + "for frame order column of type `timestamptz`, offset should not have non-zero `month` and `day`", + ); + } + } + }, + _ => unreachable!("other order column data types are not supported and should be banned in frontend"), + } + Ok(()) + }) + } +} + +impl RangeFrameBounds { + /// Get the frame start for a given order column value. + /// + /// ## Examples + /// + /// For the following frames: + /// + /// ```sql + /// ORDER BY x ASC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + /// ORDER BY x DESC RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + /// ``` + /// + /// For any CURRENT ROW with any order value, the frame start is always the first-most row, which is + /// represented by [`Sentinelled::Smallest`]. + /// + /// For the following frame: + /// + /// ```sql + /// ORDER BY x ASC RANGE BETWEEN 10 PRECEDING AND CURRENT ROW + /// ``` + /// + /// For CURRENT ROW with order value `100`, the frame start is the **FIRST** row with order value `90`. + /// + /// For the following frame: + /// + /// ```sql + /// ORDER BY x DESC RANGE BETWEEN 10 PRECEDING AND CURRENT ROW + /// ``` + /// + /// For CURRENT ROW with order value `100`, the frame start is the **FIRST** row with order value `110`. + pub fn frame_start_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { + self.start.for_calc().bound_of(order_value, self.order_type) + } + + /// Get the frame end for a given order column value. It's very similar to `frame_start_of`, just with + /// everything on the other direction. + pub fn frame_end_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { + self.end.for_calc().bound_of(order_value, self.order_type) + } + + /// Get the order value of the CURRENT ROW of the first frame that includes the given order value. + /// + /// ## Examples + /// + /// For the following frames: + /// + /// ```sql + /// ORDER BY x ASC RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + /// ORDER BY x DESC RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING + /// ``` + /// + /// For any given order value, the first CURRENT ROW is always the first-most row, which is + /// represented by [`Sentinelled::Smallest`]. + /// + /// For the following frame: + /// + /// ```sql + /// ORDER BY x ASC RANGE BETWEEN CURRENT ROW AND 10 FOLLOWING + /// ``` + /// + /// For a given order value `100`, the first CURRENT ROW should have order value `90`. + /// + /// For the following frame: + /// + /// ```sql + /// ORDER BY x DESC RANGE BETWEEN CURRENT ROW AND 10 FOLLOWING + /// ``` + /// + /// For a given order value `100`, the first CURRENT ROW should have order value `110`. + pub fn first_curr_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { + self.end + .for_calc() + .reverse() + .bound_of(order_value, self.order_type) + } + + /// Get the order value of the CURRENT ROW of the last frame that includes the given order value. + /// It's very similar to `first_curr_of`, just with everything on the other direction. + pub fn last_curr_of(&self, order_value: impl ToOwnedDatum) -> Sentinelled { + self.start + .for_calc() + .reverse() + .bound_of(order_value, self.order_type) + } +} + +pub type RangeFrameBound = FrameBound; + +impl RangeFrameBound { + fn from_protobuf( + bound: &PbRangeFrameBound, + order_data_type: &DataType, + offset_data_type: &DataType, + ) -> Result { + let bound = match bound.get_type()? { + PbBoundType::Unspecified => bail!("unspecified type of `RangeFrameBound`"), + PbBoundType::UnboundedPreceding => Self::UnboundedPreceding, + PbBoundType::CurrentRow => Self::CurrentRow, + PbBoundType::UnboundedFollowing => Self::UnboundedFollowing, + bound_type @ (PbBoundType::Preceding | PbBoundType::Following) => { + let offset_value = Datum::from_protobuf(bound.get_offset()?, offset_data_type) + .context("offset `Datum` is not decodable")? + .context("offset of `RangeFrameBound` must be non-NULL")?; + let mut offset = RangeFrameOffset::new(offset_value); + offset.prepare(order_data_type, offset_data_type)?; + if bound_type == PbBoundType::Preceding { + Self::Preceding(offset) + } else { + Self::Following(offset) + } + } + }; + Ok(bound) + } + + fn to_protobuf(&self) -> PbRangeFrameBound { + let (r#type, offset) = match self { + Self::UnboundedPreceding => (PbBoundType::UnboundedPreceding, None), + Self::Preceding(offset) => ( + PbBoundType::Preceding, + Some(Some(offset.as_scalar_ref_impl()).to_protobuf()), + ), + Self::CurrentRow => (PbBoundType::CurrentRow, None), + Self::Following(offset) => ( + PbBoundType::Following, + Some(Some(offset.as_scalar_ref_impl()).to_protobuf()), + ), + Self::UnboundedFollowing => (PbBoundType::UnboundedFollowing, None), + }; + PbRangeFrameBound { + r#type: r#type as _, + offset, + } + } +} + +impl RangeFrameBound { + fn for_display(&self) -> FrameBound { + match self { + UnboundedPreceding => UnboundedPreceding, + Preceding(offset) => Preceding(offset.as_scalar_ref_impl().to_text()), + CurrentRow => CurrentRow, + Following(offset) => Following(offset.as_scalar_ref_impl().to_text()), + UnboundedFollowing => UnboundedFollowing, + } + } + + fn for_calc(&self) -> FrameBound> { + match self { + UnboundedPreceding => UnboundedPreceding, + Preceding(offset) => Preceding(RangeFrameOffsetRef { + add_expr: offset.add_expr.as_ref().unwrap().as_ref(), + sub_expr: offset.sub_expr.as_ref().unwrap().as_ref(), + }), + CurrentRow => CurrentRow, + Following(offset) => Following(RangeFrameOffsetRef { + add_expr: offset.add_expr.as_ref().unwrap().as_ref(), + sub_expr: offset.sub_expr.as_ref().unwrap().as_ref(), + }), + UnboundedFollowing => UnboundedFollowing, + } + } +} + +/// The wrapper type for [`ScalarImpl`] range frame offset, containing +/// two expressions to help adding and subtracting the offset. +#[derive(Debug, Clone, Educe)] +#[educe(PartialEq, Eq, Hash)] +pub struct RangeFrameOffset { + /// The original offset value. + offset: ScalarImpl, + /// Built expression for `$0 + offset`. + #[educe(PartialEq(ignore), Hash(ignore))] + add_expr: Option>, + /// Built expression for `$0 - offset`. + #[educe(PartialEq(ignore), Hash(ignore))] + sub_expr: Option>, +} + +impl RangeFrameOffset { + pub fn new(offset: ScalarImpl) -> Self { + Self { + offset, + add_expr: None, + sub_expr: None, + } + } + + fn prepare(&mut self, order_data_type: &DataType, offset_data_type: &DataType) -> Result<()> { + use risingwave_pb::expr::expr_node::PbType as PbExprType; + + let input_expr = InputRefExpression::new(order_data_type.clone(), 0); + let offset_expr = + LiteralExpression::new(offset_data_type.clone(), Some(self.offset.clone())); + self.add_expr = Some(Arc::new(build_func( + PbExprType::Add, + order_data_type.clone(), + vec![input_expr.clone().boxed(), offset_expr.clone().boxed()], + )?)); + self.sub_expr = Some(Arc::new(build_func( + PbExprType::Subtract, + order_data_type.clone(), + vec![input_expr.boxed(), offset_expr.boxed()], + )?)); + Ok(()) + } + + pub fn new_for_test( + offset: ScalarImpl, + order_data_type: &DataType, + offset_data_type: &DataType, + ) -> Self { + let mut offset = Self::new(offset); + offset.prepare(order_data_type, offset_data_type).unwrap(); + offset + } +} + +impl Deref for RangeFrameOffset { + type Target = ScalarImpl; + + fn deref(&self) -> &Self::Target { + &self.offset + } +} + +#[derive(Debug, Educe)] +#[educe(Clone, Copy)] +struct RangeFrameOffsetRef<'a> { + /// Built expression for `$0 + offset`. + add_expr: &'a dyn Expression, + /// Built expression for `$0 - offset`. + sub_expr: &'a dyn Expression, +} + +impl FrameBound> { + fn bound_of(self, order_value: impl ToOwnedDatum, order_type: OrderType) -> Sentinelled { + let expr = match (self, order_type.direction()) { + (UnboundedPreceding, _) => return Sentinelled::Smallest, + (UnboundedFollowing, _) => return Sentinelled::Largest, + (CurrentRow, _) => return Sentinelled::Normal(order_value.to_owned_datum()), + (Preceding(offset), Direction::Ascending) + | (Following(offset), Direction::Descending) => { + // should SUBTRACT the offset + offset.sub_expr + } + (Following(offset), Direction::Ascending) + | (Preceding(offset), Direction::Descending) => { + // should ADD the offset + offset.add_expr + } + }; + let row = OwnedRow::new(vec![order_value.to_owned_datum()]); + Sentinelled::Normal( + expr.eval_row(&row) + .now_or_never() + .expect("frame bound calculation should finish immediately") + .expect("just simple calculation, should succeed"), // TODO(rc): handle overflow + ) + } +} diff --git a/src/expr/core/src/window_function/rows.rs b/src/expr/core/src/window_function/rows.rs new file mode 100644 index 0000000000000..61eda77b65ef5 --- /dev/null +++ b/src/expr/core/src/window_function/rows.rs @@ -0,0 +1,234 @@ +// Copyright 2024 RisingWave Labs +// +// 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 parse_display::Display; +use risingwave_common::bail; +use risingwave_pb::expr::window_frame::{ + PbBound, PbBoundType, PbRowsFrameBound, PbRowsFrameBounds, +}; + +use super::FrameBound::{ + self, CurrentRow, Following, Preceding, UnboundedFollowing, UnboundedPreceding, +}; +use super::FrameBoundsImpl; +use crate::Result; + +#[derive(Display, Debug, Clone, Eq, PartialEq, Hash)] +#[display("ROWS BETWEEN {start} AND {end}")] +pub struct RowsFrameBounds { + pub start: RowsFrameBound, + pub end: RowsFrameBound, +} + +impl RowsFrameBounds { + pub(super) fn from_protobuf(bounds: &PbRowsFrameBounds) -> Result { + let start = FrameBound::::from_protobuf(bounds.get_start()?)?; + let end = FrameBound::::from_protobuf(bounds.get_end()?)?; + Ok(Self { start, end }) + } + + pub(super) fn to_protobuf(&self) -> PbRowsFrameBounds { + PbRowsFrameBounds { + start: Some(self.start.to_protobuf()), + end: Some(self.end.to_protobuf()), + } + } +} + +impl RowsFrameBounds { + /// Check if the `ROWS` frame is canonical. + /// + /// A canonical `ROWS` frame is defined as: + /// + /// - Its bounds are valid (see [`Self::validate`]). + /// - It contains the current row. + pub fn is_canonical(&self) -> bool { + self.validate().is_ok() && { + let start = self.start.to_offset(); + let end = self.end.to_offset(); + start.unwrap_or(0) <= 0 && end.unwrap_or(0) >= 0 + } + } + + /// Get the number of preceding rows. + pub fn n_preceding_rows(&self) -> Option { + match (&self.start, &self.end) { + (UnboundedPreceding, _) => None, + (Preceding(n1), Preceding(n2)) => Some(*n1.max(n2)), + (Preceding(n), _) => Some(*n), + (CurrentRow | Following(_) | UnboundedFollowing, _) => Some(0), + } + } + + /// Get the number of following rows. + pub fn n_following_rows(&self) -> Option { + match (&self.start, &self.end) { + (_, UnboundedFollowing) => None, + (Following(n1), Following(n2)) => Some(*n1.max(n2)), + (_, Following(n)) => Some(*n), + (_, CurrentRow | Preceding(_) | UnboundedPreceding) => Some(0), + } + } +} + +impl FrameBoundsImpl for RowsFrameBounds { + fn validate(&self) -> Result<()> { + FrameBound::validate_bounds(&self.start, &self.end, |_| Ok(())) + } +} + +pub type RowsFrameBound = FrameBound; + +impl RowsFrameBound { + pub(super) fn from_protobuf_legacy(bound: &PbBound) -> Result { + use risingwave_pb::expr::window_frame::bound::PbOffset; + + let offset = bound.get_offset()?; + let bound = match offset { + PbOffset::Integer(offset) => Self::from_protobuf(&PbRowsFrameBound { + r#type: bound.get_type()? as _, + offset: Some(*offset), + })?, + PbOffset::Datum(_) => bail!("offset of `RowsFrameBound` must be `Integer`"), + }; + Ok(bound) + } + + fn from_protobuf(bound: &PbRowsFrameBound) -> Result { + let bound = match bound.get_type()? { + PbBoundType::Unspecified => bail!("unspecified type of `RowsFrameBound`"), + PbBoundType::UnboundedPreceding => Self::UnboundedPreceding, + PbBoundType::Preceding => Self::Preceding(*bound.get_offset()? as usize), + PbBoundType::CurrentRow => Self::CurrentRow, + PbBoundType::Following => Self::Following(*bound.get_offset()? as usize), + PbBoundType::UnboundedFollowing => Self::UnboundedFollowing, + }; + Ok(bound) + } + + fn to_protobuf(&self) -> PbRowsFrameBound { + let (r#type, offset) = match self { + Self::UnboundedPreceding => (PbBoundType::UnboundedPreceding, None), + Self::Preceding(offset) => (PbBoundType::Preceding, Some(*offset as _)), + Self::CurrentRow => (PbBoundType::CurrentRow, None), + Self::Following(offset) => (PbBoundType::Following, Some(*offset as _)), + Self::UnboundedFollowing => (PbBoundType::UnboundedFollowing, None), + }; + PbRowsFrameBound { + r#type: r#type as _, + offset, + } + } +} + +impl RowsFrameBound { + /// Convert the bound to sized offset from current row. `None` if the bound is unbounded. + pub fn to_offset(&self) -> Option { + match self { + UnboundedPreceding | UnboundedFollowing => None, + CurrentRow => Some(0), + Preceding(n) => Some(-(*n as isize)), + Following(n) => Some(*n as isize), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_rows_frame_bounds() { + let bounds = RowsFrameBounds { + start: Preceding(1), + end: CurrentRow, + }; + assert!(bounds.validate().is_ok()); + assert!(bounds.is_canonical()); + assert_eq!(bounds.start.to_offset(), Some(-1)); + assert_eq!(bounds.end.to_offset(), Some(0)); + assert_eq!(bounds.n_preceding_rows(), Some(1)); + assert_eq!(bounds.n_following_rows(), Some(0)); + + let bounds = RowsFrameBounds { + start: CurrentRow, + end: Following(1), + }; + assert!(bounds.validate().is_ok()); + assert!(bounds.is_canonical()); + assert_eq!(bounds.start.to_offset(), Some(0)); + assert_eq!(bounds.end.to_offset(), Some(1)); + assert_eq!(bounds.n_preceding_rows(), Some(0)); + assert_eq!(bounds.n_following_rows(), Some(1)); + + let bounds = RowsFrameBounds { + start: UnboundedPreceding, + end: Following(10), + }; + assert!(bounds.validate().is_ok()); + assert!(bounds.is_canonical()); + assert_eq!(bounds.start.to_offset(), None); + assert_eq!(bounds.end.to_offset(), Some(10)); + assert_eq!(bounds.n_preceding_rows(), None); + assert_eq!(bounds.n_following_rows(), Some(10)); + + let bounds = RowsFrameBounds { + start: Preceding(10), + end: UnboundedFollowing, + }; + assert!(bounds.validate().is_ok()); + assert!(bounds.is_canonical()); + assert_eq!(bounds.start.to_offset(), Some(-10)); + assert_eq!(bounds.end.to_offset(), None); + assert_eq!(bounds.n_preceding_rows(), Some(10)); + assert_eq!(bounds.n_following_rows(), None); + + let bounds = RowsFrameBounds { + start: Preceding(1), + end: Preceding(10), + }; + assert!(bounds.validate().is_ok()); + assert!(!bounds.is_canonical()); + assert_eq!(bounds.start.to_offset(), Some(-1)); + assert_eq!(bounds.end.to_offset(), Some(-10)); + assert_eq!(bounds.n_preceding_rows(), Some(10)); + assert_eq!(bounds.n_following_rows(), Some(0)); + + let bounds = RowsFrameBounds { + start: Following(10), + end: Following(1), + }; + assert!(bounds.validate().is_ok()); + assert!(!bounds.is_canonical()); + assert_eq!(bounds.start.to_offset(), Some(10)); + assert_eq!(bounds.end.to_offset(), Some(1)); + assert_eq!(bounds.n_preceding_rows(), Some(0)); + assert_eq!(bounds.n_following_rows(), Some(10)); + + let bounds = RowsFrameBounds { + start: UnboundedFollowing, + end: Following(10), + }; + assert!(bounds.validate().is_err()); + assert!(!bounds.is_canonical()); + + let bounds = RowsFrameBounds { + start: Preceding(10), + end: UnboundedPreceding, + }; + assert!(bounds.validate().is_err()); + assert!(!bounds.is_canonical()); + } +} diff --git a/src/expr/core/src/window_function/session.rs b/src/expr/core/src/window_function/session.rs new file mode 100644 index 0000000000000..81a77058759bf --- /dev/null +++ b/src/expr/core/src/window_function/session.rs @@ -0,0 +1,208 @@ +// Copyright 2024 RisingWave Labs +// +// 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::Display; +use std::ops::Deref; +use std::sync::Arc; + +use anyhow::Context; +use educe::Educe; +use futures::FutureExt; +use risingwave_common::bail; +use risingwave_common::row::OwnedRow; +use risingwave_common::types::{ + DataType, Datum, IsNegative, ScalarImpl, ScalarRefImpl, ToOwnedDatum, ToText, +}; +use risingwave_common::util::sort_util::OrderType; +use risingwave_common::util::value_encoding::{DatumFromProtoExt, DatumToProtoExt}; +use risingwave_pb::expr::window_frame::PbSessionFrameBounds; + +use super::FrameBoundsImpl; +use crate::expr::{ + build_func, BoxedExpression, Expression, ExpressionBoxExt, InputRefExpression, + LiteralExpression, +}; +use crate::Result; + +/// To implement Session Window in a similar way to Range Frame, we define a similar frame bounds +/// structure here. It's very like [`RangeFrameBounds`](super::RangeFrameBounds), but with a gap +/// instead of start & end offset. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct SessionFrameBounds { + pub order_data_type: DataType, + pub order_type: OrderType, + pub gap_data_type: DataType, + pub gap: SessionFrameGap, +} + +impl SessionFrameBounds { + pub(super) fn from_protobuf(bounds: &PbSessionFrameBounds) -> Result { + let order_data_type = DataType::from(bounds.get_order_data_type()?); + let order_type = OrderType::from_protobuf(bounds.get_order_type()?); + let gap_data_type = DataType::from(bounds.get_gap_data_type()?); + let gap_value = Datum::from_protobuf(bounds.get_gap()?, &gap_data_type) + .context("gap `Datum` is not decodable")? + .context("gap of session frame must be non-NULL")?; + let mut gap = SessionFrameGap::new(gap_value); + gap.prepare(&order_data_type, &gap_data_type)?; + Ok(Self { + order_data_type, + order_type, + gap_data_type, + gap, + }) + } + + pub(super) fn to_protobuf(&self) -> PbSessionFrameBounds { + PbSessionFrameBounds { + gap: Some(Some(self.gap.as_scalar_ref_impl()).to_protobuf()), + order_data_type: Some(self.order_data_type.to_protobuf()), + order_type: Some(self.order_type.to_protobuf()), + gap_data_type: Some(self.gap_data_type.to_protobuf()), + } + } +} + +impl Display for SessionFrameBounds { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "SESSION WITH GAP {}", + self.gap.as_scalar_ref_impl().to_text() + ) + } +} + +impl FrameBoundsImpl for SessionFrameBounds { + fn validate(&self) -> Result<()> { + // TODO(rc): maybe can merge with `RangeFrameBounds::validate` + + fn validate_non_negative(val: impl IsNegative + Display) -> Result<()> { + if val.is_negative() { + bail!("session gap should be non-negative, but {} is given", val); + } + Ok(()) + } + + match self.gap.as_scalar_ref_impl() { + ScalarRefImpl::Int16(val) => validate_non_negative(val)?, + ScalarRefImpl::Int32(val) => validate_non_negative(val)?, + ScalarRefImpl::Int64(val) => validate_non_negative(val)?, + ScalarRefImpl::Float32(val) => validate_non_negative(val)?, + ScalarRefImpl::Float64(val) => validate_non_negative(val)?, + ScalarRefImpl::Decimal(val) => validate_non_negative(val)?, + ScalarRefImpl::Interval(val) => { + if !val.is_never_negative() { + bail!( + "for session gap of type `interval`, each field should be non-negative, but {} is given", + val + ); + } + if matches!(self.order_data_type, DataType::Timestamptz) { + // for `timestamptz`, we only support gap without `month` and `day` fields + if val.months() != 0 || val.days() != 0 { + bail!( + "for session order column of type `timestamptz`, gap should not have non-zero `month` and `day`", + ); + } + } + } + _ => unreachable!( + "other order column data types are not supported and should be banned in frontend" + ), + } + Ok(()) + } +} + +impl SessionFrameBounds { + pub fn minimal_next_start_of(&self, end_order_value: impl ToOwnedDatum) -> Datum { + self.gap.for_calc().minimal_next_start_of(end_order_value) + } +} + +/// The wrapper type for [`ScalarImpl`] session gap, containing an expression to help adding the gap +/// to a given value. +#[derive(Debug, Clone, Educe)] +#[educe(PartialEq, Eq, Hash)] +pub struct SessionFrameGap { + /// The original gap value. + gap: ScalarImpl, + /// Built expression for `$0 + gap`. + #[educe(PartialEq(ignore), Hash(ignore))] + add_expr: Option>, +} + +impl Deref for SessionFrameGap { + type Target = ScalarImpl; + + fn deref(&self) -> &Self::Target { + &self.gap + } +} + +impl SessionFrameGap { + pub fn new(gap: ScalarImpl) -> Self { + Self { + gap, + add_expr: None, + } + } + + fn prepare(&mut self, order_data_type: &DataType, gap_data_type: &DataType) -> Result<()> { + use risingwave_pb::expr::expr_node::PbType as PbExprType; + + let input_expr = InputRefExpression::new(order_data_type.clone(), 0); + let gap_expr = LiteralExpression::new(gap_data_type.clone(), Some(self.gap.clone())); + self.add_expr = Some(Arc::new(build_func( + PbExprType::Add, + order_data_type.clone(), + vec![input_expr.clone().boxed(), gap_expr.clone().boxed()], + )?)); + Ok(()) + } + + pub fn new_for_test( + gap: ScalarImpl, + order_data_type: &DataType, + gap_data_type: &DataType, + ) -> Self { + let mut gap = Self::new(gap); + gap.prepare(order_data_type, gap_data_type).unwrap(); + gap + } + + fn for_calc(&self) -> SessionFrameGapRef<'_> { + SessionFrameGapRef { + add_expr: self.add_expr.as_ref().unwrap().as_ref(), + } + } +} + +#[derive(Debug, Educe)] +#[educe(Clone, Copy)] +struct SessionFrameGapRef<'a> { + add_expr: &'a dyn Expression, +} + +impl<'a> SessionFrameGapRef<'a> { + fn minimal_next_start_of(&self, end_order_value: impl ToOwnedDatum) -> Datum { + let row = OwnedRow::new(vec![end_order_value.to_owned_datum()]); + self.add_expr + .eval_row(&row) + .now_or_never() + .expect("frame bound calculation should finish immediately") + .expect("just simple calculation, should succeed") // TODO(rc): handle overflow + } +} diff --git a/src/expr/core/src/window_function/state/mod.rs b/src/expr/core/src/window_function/state.rs similarity index 87% rename from src/expr/core/src/window_function/state/mod.rs rename to src/expr/core/src/window_function/state.rs index 6a86667bfcafc..ec5bfc70d8591 100644 --- a/src/expr/core/src/window_function/state/mod.rs +++ b/src/expr/core/src/window_function/state.rs @@ -21,14 +21,9 @@ use risingwave_common::util::memcmp_encoding::MemcmpEncoded; use risingwave_common_estimate_size::EstimateSize; use smallvec::SmallVec; -use super::{WindowFuncCall, WindowFuncKind}; +use super::WindowFuncCall; use crate::{ExprError, Result}; -mod aggregate; -mod buffer; -mod range_utils; -mod rank; - /// Unique and ordered identifier for a row in internal states. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, EstimateSize)] pub struct StateKey { @@ -110,22 +105,21 @@ pub trait WindowState: EstimateSize { pub type BoxedWindowState = Box; -pub fn create_window_state(call: &WindowFuncCall) -> Result { - assert!(call.frame.bounds.validate().is_ok()); +#[linkme::distributed_slice] +pub static WINDOW_STATE_BUILDERS: [fn(&WindowFuncCall) -> Result]; - use WindowFuncKind::*; - Ok(match call.kind { - RowNumber => Box::new(rank::RankState::::new(call)), - Rank => Box::new(rank::RankState::::new(call)), - DenseRank => Box::new(rank::RankState::::new(call)), - Aggregate(_) => aggregate::new(call)?, - kind => { - return Err(ExprError::UnsupportedFunction(format!( +pub fn create_window_state(call: &WindowFuncCall) -> Result { + // we expect only one builder function in `expr_impl/window_function/mod.rs` + let builder = WINDOW_STATE_BUILDERS.iter().next(); + builder.map_or_else( + || { + Err(ExprError::UnsupportedFunction(format!( "{}({}) -> {}", - kind, + call.kind, call.args.arg_types().iter().format(", "), &call.return_type, - ))); - } - }) + ))) + }, + |f| f(call), + ) } diff --git a/src/expr/impl/Cargo.toml b/src/expr/impl/Cargo.toml index 082f33de2da4e..0f69c91e34162 100644 --- a/src/expr/impl/Cargo.toml +++ b/src/expr/impl/Cargo.toml @@ -15,44 +15,66 @@ ignored = ["workspace-hack", "ctor"] [package.metadata.cargo-udeps.ignore] normal = ["workspace-hack", "ctor"] +[features] +external-udf = ["arrow-udf-flight", "arrow-flight", "tonic"] +js-udf = ["arrow-udf-js"] +deno-udf = ["arrow-udf-js-deno", "zstd"] +python-udf = ["arrow-udf-python"] +wasm-udf = ["arrow-udf-wasm", "zstd"] + [dependencies] aho-corasick = "1" anyhow = "1" +arrow-array = { workspace = true } +arrow-flight = { workspace = true, optional = true } arrow-schema = { workspace = true } +arrow-schema-iceberg = { workspace = true } +arrow-udf-flight = { workspace = true, optional = true } +arrow-udf-js = { workspace = true, optional = true } +arrow-udf-js-deno = { workspace = true, optional = true } +arrow-udf-python = { workspace = true, optional = true } +arrow-udf-wasm = { workspace = true, optional = true } async-trait = "0.1" auto_enums = { workspace = true } chrono = { version = "0.4", default-features = false, features = [ "clock", "std", ] } -chrono-tz = { version = "0.8", features = ["case-insensitive"] } +chrono-tz = { version = "0.9", features = ["case-insensitive"] } +educe = "0.6" fancy-regex = "0.13" futures-async-stream = { workspace = true } futures-util = "0.3" +ginepro = "0.7" hex = "0.4" icelake = { workspace = true } itertools = { workspace = true } -jsonbb = "0.1.2" +jsonbb = { workspace = true } linkme = { version = "0.3", features = ["used_linker"] } md5 = "0.7" +moka = { version = "0.12", features = ["sync"] } num-traits = "0.2" -openssl = { version = "0.10", features = ["vendored"] } +openssl = "0.10" regex = "1" risingwave_common = { workspace = true } risingwave_common_estimate_size = { workspace = true } risingwave_expr = { workspace = true } risingwave_pb = { workspace = true } +risingwave_sqlparser = { workspace = true } rust_decimal = { version = "1", features = ["db-postgres", "maths"] } self_cell = "1.0.1" serde = { version = "1", features = ["derive"] } serde_json = "1" sha1 = "0.10" sha2 = "0.10" +smallvec = "1" sql-json-path = { version = "0.1", features = ["jsonbb"] } thiserror = "1" thiserror-ext = { workspace = true } tokio = { version = "0.2", package = "madsim-tokio", features = ["time"] } +tonic = { version = "0.10", optional = true } tracing = "0.1" +zstd = { version = "0.13", default-features = false, optional = true } [target.'cfg(not(madsim))'.dependencies] workspace-hack = { path = "../../workspace-hack" } diff --git a/src/expr/impl/benches/expr.rs b/src/expr/impl/benches/expr.rs index 8468ae86e241b..21036d3649acb 100644 --- a/src/expr/impl/benches/expr.rs +++ b/src/expr/impl/benches/expr.rs @@ -26,10 +26,9 @@ use criterion::{criterion_group, criterion_main, Criterion}; use risingwave_common::array::*; use risingwave_common::types::test_utils::IntervalTestExt; use risingwave_common::types::*; -use risingwave_expr::aggregate::{build_append_only, AggArgs, AggCall, AggKind}; +use risingwave_expr::aggregate::{build_append_only, AggCall, AggKind}; use risingwave_expr::expr::*; use risingwave_expr::sig::FUNCTION_REGISTRY; -use risingwave_expr::ExprError; use risingwave_pb::expr::expr_node::PbType; use thiserror_ext::AsReport; @@ -397,26 +396,17 @@ fn bench_expr(c: &mut Criterion) { } let agg = match build_append_only(&AggCall { kind: sig.name.as_aggregate(), - args: match sig.inputs_type.as_slice() { - [] => AggArgs::None, - [t] => AggArgs::Unary(t.as_exact().clone(), input_index_for_type(t.as_exact())), - [t1, t2] => AggArgs::Binary( - [t1.as_exact().clone(), t2.as_exact().clone()], - [ - input_index_for_type(t1.as_exact()), - input_index_for_type(t2.as_exact()), - ], - ), - _ => { - println!("todo: {sig:?}"); - continue; - } - }, + args: sig + .inputs_type + .iter() + .map(|t| (t.as_exact().clone(), input_index_for_type(t.as_exact()))) + .collect(), return_type: sig.ret_type.as_exact().clone(), column_orders: vec![], filter: None, distinct: false, direct_args: vec![], + user_defined: None, }) { Ok(agg) => agg, Err(e) => { @@ -434,9 +424,10 @@ fn bench_expr(c: &mut Criterion) { _ => unreachable!(), }; c.bench_function(&format!("{sig:?}"), |bencher| { - bencher - .to_async(FuturesExecutor) - .iter(|| async { agg.update(&mut agg.create_state(), &input).await.unwrap() }) + bencher.to_async(FuturesExecutor).iter(|| async { + let mut state = agg.create_state().unwrap(); + agg.update(&mut state, &input).await.unwrap() + }) }); } } diff --git a/src/expr/impl/src/aggregate/approx_count_distinct/mod.rs b/src/expr/impl/src/aggregate/approx_count_distinct/mod.rs index bfd1548963d56..a9cea212d3492 100644 --- a/src/expr/impl/src/aggregate/approx_count_distinct/mod.rs +++ b/src/expr/impl/src/aggregate/approx_count_distinct/mod.rs @@ -60,8 +60,8 @@ impl AggregateFunction for UpdatableApproxCountDistinct { DataType::Int64 } - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::::default()) + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::::default())) } async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { @@ -122,8 +122,8 @@ impl AggregateFunction for AppendOnlyApproxCountDistinct { DataType::Int64 } - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::::default()) + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::::default())) } async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { @@ -344,7 +344,7 @@ mod tests { for range in [0..20000, 20000..30000, 30000..35000] { let col = I32Array::from_iter(range.clone()).into_ref(); let input = StreamChunk::from(DataChunk::new(vec![col], range.len())); - let mut state = approx_count_distinct.create_state(); + let mut state = approx_count_distinct.create_state().unwrap(); approx_count_distinct .update(&mut state, &input) .now_or_never() diff --git a/src/expr/impl/src/aggregate/array_agg.rs b/src/expr/impl/src/aggregate/array_agg.rs index f656ea9b3e614..5ef268877b511 100644 --- a/src/expr/impl/src/aggregate/array_agg.rs +++ b/src/expr/impl/src/aggregate/array_agg.rs @@ -64,7 +64,7 @@ mod tests { + 789", ); let array_agg = build_append_only(&AggCall::from_pretty("(array_agg:int4[] $0:int4)"))?; - let mut state = array_agg.create_state(); + let mut state = array_agg.create_state()?; array_agg.update(&mut state, &chunk).await?; let actual = array_agg.get_result(&state).await?; assert_eq!(actual, Some(ListValue::from_iter([123, 456, 789]).into())); @@ -74,7 +74,7 @@ mod tests { #[tokio::test] async fn test_array_agg_empty() -> Result<()> { let array_agg = build_append_only(&AggCall::from_pretty("(array_agg:int4[] $0:int4)"))?; - let mut state = array_agg.create_state(); + let mut state = array_agg.create_state()?; assert_eq!(array_agg.get_result(&state).await?, None); diff --git a/src/expr/impl/src/aggregate/general.rs b/src/expr/impl/src/aggregate/general.rs index 2ee55f0a16d52..0c94312335b4b 100644 --- a/src/expr/impl/src/aggregate/general.rs +++ b/src/expr/impl/src/aggregate/general.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::From; - use num_traits::{CheckedAdd, CheckedSub}; use risingwave_expr::{aggregate, ExprError, Result}; @@ -205,7 +203,7 @@ mod tests { fn test_agg(pretty: &str, input: StreamChunk, expected: Datum) { let agg = build_append_only(&AggCall::from_pretty(pretty)).unwrap(); - let mut state = agg.create_state(); + let mut state = agg.create_state().unwrap(); agg.update(&mut state, &input) .now_or_never() .unwrap() @@ -473,7 +471,7 @@ mod tests { let chunk = StreamChunk::from_parts(ops, DataChunk::new(vec![Arc::new(data)], vis)); let pretty = format!("({agg_desc}:int8 $0:int8)"); let agg = build_append_only(&AggCall::from_pretty(pretty)).unwrap(); - let mut state = agg.create_state(); + let mut state = agg.create_state().unwrap(); b.iter(|| { agg.update(&mut state, &chunk) .now_or_never() diff --git a/src/expr/impl/src/aggregate/mode.rs b/src/expr/impl/src/aggregate/mode.rs index e5ebdf5a485ad..6e7954553a3e1 100644 --- a/src/expr/impl/src/aggregate/mode.rs +++ b/src/expr/impl/src/aggregate/mode.rs @@ -101,8 +101,8 @@ impl AggregateFunction for Mode { self.return_type.clone() } - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::::default()) + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::::default())) } async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { diff --git a/src/expr/impl/src/aggregate/percentile_cont.rs b/src/expr/impl/src/aggregate/percentile_cont.rs index 728495edb1122..a60f21a3099fd 100644 --- a/src/expr/impl/src/aggregate/percentile_cont.rs +++ b/src/expr/impl/src/aggregate/percentile_cont.rs @@ -91,8 +91,8 @@ impl AggregateFunction for PercentileCont { DataType::Float64 } - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::::default()) + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::::default())) } async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { diff --git a/src/expr/impl/src/aggregate/percentile_disc.rs b/src/expr/impl/src/aggregate/percentile_disc.rs index 8b0114be1b8d5..23285ab5ef293 100644 --- a/src/expr/impl/src/aggregate/percentile_disc.rs +++ b/src/expr/impl/src/aggregate/percentile_disc.rs @@ -116,8 +116,8 @@ impl AggregateFunction for PercentileDisc { self.return_type.clone() } - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::::default()) + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::::default())) } async fn update(&self, state: &mut AggregateState, input: &StreamChunk) -> Result<()> { diff --git a/src/expr/impl/src/aggregate/string_agg.rs b/src/expr/impl/src/aggregate/string_agg.rs index 32e1888cdfc2b..0195f2b47b6b2 100644 --- a/src/expr/impl/src/aggregate/string_agg.rs +++ b/src/expr/impl/src/aggregate/string_agg.rs @@ -48,7 +48,7 @@ mod tests { let string_agg = build_append_only(&AggCall::from_pretty( "(string_agg:varchar $0:varchar $1:varchar)", ))?; - let mut state = string_agg.create_state(); + let mut state = string_agg.create_state()?; string_agg.update(&mut state, &chunk).await?; assert_eq!( string_agg.get_result(&state).await?, @@ -69,7 +69,7 @@ mod tests { let string_agg = build_append_only(&AggCall::from_pretty( "(string_agg:varchar $0:varchar $1:varchar)", ))?; - let mut state = string_agg.create_state(); + let mut state = string_agg.create_state()?; string_agg.update(&mut state, &chunk).await?; assert_eq!( string_agg.get_result(&state).await?, diff --git a/src/expr/impl/src/lib.rs b/src/expr/impl/src/lib.rs index a47754cb4e821..56bdbe3b81100 100644 --- a/src/expr/impl/src/lib.rs +++ b/src/expr/impl/src/lib.rs @@ -27,7 +27,6 @@ #![feature(iterator_try_collect)] #![feature(exclusive_range_pattern)] #![feature(lazy_cell)] -#![feature(round_ties_even)] #![feature(coroutines)] #![feature(test)] #![feature(iter_array_chunks)] @@ -37,6 +36,8 @@ mod aggregate; mod scalar; mod table_function; +mod udf; +mod window_function; /// Enable functions in this crate. #[macro_export] diff --git a/src/expr/impl/src/scalar/arithmetic_op.rs b/src/expr/impl/src/scalar/arithmetic_op.rs index ad5fe8497d8bb..e48b0b5e94b59 100644 --- a/src/expr/impl/src/scalar/arithmetic_op.rs +++ b/src/expr/impl/src/scalar/arithmetic_op.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::TryInto; use std::fmt::Debug; use chrono::{Duration, NaiveDateTime}; @@ -443,9 +442,7 @@ mod tests { use num_traits::Float; use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::{ - Date, Decimal, Int256, Int256Ref, Interval, Scalar, Timestamp, F32, F64, - }; + use risingwave_common::types::{Int256, Int256Ref, Scalar, F32}; use super::*; diff --git a/src/expr/impl/src/scalar/bitwise_op.rs b/src/expr/impl/src/scalar/bitwise_op.rs index 38aa4d194ea60..0d7f14bb6965a 100644 --- a/src/expr/impl/src/scalar/bitwise_op.rs +++ b/src/expr/impl/src/scalar/bitwise_op.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::any::type_name; -use std::convert::TryInto; use std::fmt::Debug; use std::ops::{BitAnd, BitOr, BitXor, Not}; diff --git a/src/expr/impl/src/scalar/case.rs b/src/expr/impl/src/scalar/case.rs index 99ec8cb880ddc..f7fb9d89ef41b 100644 --- a/src/expr/impl/src/scalar/case.rs +++ b/src/expr/impl/src/scalar/case.rs @@ -285,7 +285,6 @@ fn build_case_expr( #[cfg(test)] mod tests { - use risingwave_common::row::Row; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::ToOwnedDatum; use risingwave_common::util::iter_util::ZipEqDebug; diff --git a/src/expr/impl/src/scalar/cast.rs b/src/expr/impl/src/scalar/cast.rs index cb990bdcec6a7..48218d7200809 100644 --- a/src/expr/impl/src/scalar/cast.rs +++ b/src/expr/impl/src/scalar/cast.rs @@ -244,11 +244,9 @@ fn struct_cast(input: StructRef<'_>, ctx: &Context) -> Result { #[cfg(test)] mod tests { use chrono::NaiveDateTime; - use itertools::Itertools; use risingwave_common::array::*; use risingwave_common::types::*; use risingwave_expr::expr::build_from_pretty; - use risingwave_pb::expr::expr_node::PbType; use super::*; diff --git a/src/expr/impl/src/scalar/cmp.rs b/src/expr/impl/src/scalar/cmp.rs index 69dfcb5f57ef3..20c0afc307e74 100644 --- a/src/expr/impl/src/scalar/cmp.rs +++ b/src/expr/impl/src/scalar/cmp.rs @@ -15,7 +15,7 @@ use std::fmt::Debug; use risingwave_common::array::{Array, BoolArray}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::row::Row; use risingwave_common::types::{Scalar, ScalarRef, ScalarRefImpl}; use risingwave_expr::function; @@ -519,7 +519,7 @@ mod tests { use risingwave_common::array::*; use risingwave_common::row::OwnedRow; use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::{Date, Interval, Scalar}; + use risingwave_common::types::{Date, Interval}; use risingwave_pb::expr::expr_node::Type; use crate::scalar::arithmetic_op::{date_interval_add, date_interval_sub}; diff --git a/src/expr/impl/src/scalar/encrypt.rs b/src/expr/impl/src/scalar/encrypt.rs index 2c7dd89ed980e..5aa2aa7dabd4e 100644 --- a/src/expr/impl/src/scalar/encrypt.rs +++ b/src/expr/impl/src/scalar/encrypt.rs @@ -18,7 +18,7 @@ use std::sync::LazyLock; use openssl::error::ErrorStack; use openssl::symm::{Cipher, Crypter, Mode as CipherMode}; use regex::Regex; -use risingwave_expr::{function, CryptographyError, CryptographyStage, ExprError, Result}; +use risingwave_expr::{function, ExprError, Result}; #[derive(Debug, Clone, PartialEq)] enum Algorithm { @@ -129,14 +129,13 @@ impl CipherConfig { }) } - fn eval(&self, input: &[u8], stage: CryptographyStage) -> Result> { + fn eval(&self, input: &[u8], stage: CryptographyStage) -> Result, CryptographyError> { let operation = match stage { CryptographyStage::Encrypt => CipherMode::Encrypt, CryptographyStage::Decrypt => CipherMode::Decrypt, }; - self.eval_inner(input, operation).map_err(|reason| { - ExprError::Cryptography(Box::new(CryptographyError { stage, reason })) - }) + self.eval_inner(input, operation) + .map_err(|reason| CryptographyError { stage, reason }) } fn eval_inner( @@ -163,7 +162,7 @@ impl CipherConfig { "decrypt(bytea, bytea, varchar) -> bytea", prebuild = "CipherConfig::parse_cipher_config($1, $2)?" )] -pub fn decrypt(data: &[u8], config: &CipherConfig) -> Result> { +fn decrypt(data: &[u8], config: &CipherConfig) -> Result, CryptographyError> { config.eval(data, CryptographyStage::Decrypt) } @@ -171,10 +170,24 @@ pub fn decrypt(data: &[u8], config: &CipherConfig) -> Result> { "encrypt(bytea, bytea, varchar) -> bytea", prebuild = "CipherConfig::parse_cipher_config($1, $2)?" )] -pub fn encrypt(data: &[u8], config: &CipherConfig) -> Result> { +fn encrypt(data: &[u8], config: &CipherConfig) -> Result, CryptographyError> { config.eval(data, CryptographyStage::Encrypt) } +#[derive(Debug)] +enum CryptographyStage { + Encrypt, + Decrypt, +} + +#[derive(Debug, thiserror::Error)] +#[error("{stage:?} stage, reason: {reason}")] +struct CryptographyError { + pub stage: CryptographyStage, + #[source] + pub reason: openssl::error::ErrorStack, +} + #[cfg(test)] mod test { use super::*; @@ -197,13 +210,13 @@ mod test { #[test] fn encrypt_testcase() { - let encrypt_wrapper = |data: &[u8], key: &[u8], mode: &str| -> Result> { - let config = CipherConfig::parse_cipher_config(key, mode)?; - encrypt(data, &config) + let encrypt_wrapper = |data: &[u8], key: &[u8], mode: &str| -> Box<[u8]> { + let config = CipherConfig::parse_cipher_config(key, mode).unwrap(); + encrypt(data, &config).unwrap() }; - let decrypt_wrapper = |data: &[u8], key: &[u8], mode: &str| -> Result> { - let config = CipherConfig::parse_cipher_config(key, mode)?; - decrypt(data, &config) + let decrypt_wrapper = |data: &[u8], key: &[u8], mode: &str| -> Box<[u8]> { + let config = CipherConfig::parse_cipher_config(key, mode).unwrap(); + decrypt(data, &config).unwrap() }; let key = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"; @@ -211,10 +224,9 @@ mod test { b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", key, "aes-ecb/pad:none", - ) - .unwrap(); + ); - let decrypted = decrypt_wrapper(&encrypted, key, "aes-ecb/pad:none").unwrap(); + let decrypted = decrypt_wrapper(&encrypted, key, "aes-ecb/pad:none"); assert_eq!( decrypted, (*b"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff").into() diff --git a/src/expr/impl/src/scalar/external/iceberg.rs b/src/expr/impl/src/scalar/external/iceberg.rs index cd616aa5e475a..902545d01c25d 100644 --- a/src/expr/impl/src/scalar/external/iceberg.rs +++ b/src/expr/impl/src/scalar/external/iceberg.rs @@ -19,10 +19,10 @@ use std::str::FromStr; use std::sync::Arc; use anyhow::anyhow; -use arrow_schema::DataType as ArrowDataType; use icelake::types::{ create_transform_function, Any as IcelakeDataType, BoxedTransformFunction, Transform, }; +use risingwave_common::array::arrow::IcebergArrowConvert; use risingwave_common::array::{ArrayRef, DataChunk}; use risingwave_common::ensure; use risingwave_common::row::OwnedRow; @@ -34,6 +34,8 @@ use thiserror_ext::AsReport; pub struct IcebergTransform { child: BoxedExpression, transform: BoxedTransformFunction, + input_arrow_type: arrow_schema_iceberg::DataType, + output_arrow_field: arrow_schema_iceberg::Field, return_type: DataType, } @@ -56,11 +58,14 @@ impl risingwave_expr::expr::Expression for IcebergTransform { // Get the child array let array = self.child.eval(data_chunk).await?; // Convert to arrow array - let arrow_array = array.as_ref().try_into().unwrap(); + let arrow_array = IcebergArrowConvert.to_arrow_array(&self.input_arrow_type, &array)?; // Transform let res_array = self.transform.transform(arrow_array).unwrap(); // Convert back to array ref and return it - Ok(Arc::new((&res_array).try_into().unwrap())) + Ok(Arc::new(IcebergArrowConvert.array_from_arrow_array( + &self.output_arrow_field, + &res_array, + )?)) } async fn eval_row(&self, _row: &OwnedRow) -> Result { @@ -91,15 +96,21 @@ fn build(return_type: DataType, mut children: Vec) -> Result) -> Result) -> Result Decimal { fn extract_from_date(date: Date, unit: &Unit) -> Decimal { match unit { Epoch => { - let epoch = date.0.and_time(NaiveTime::default()).timestamp(); + let epoch = date.0.and_time(NaiveTime::default()).and_utc().timestamp(); epoch.into() } Julian => { @@ -92,11 +92,12 @@ fn extract_from_time(time: Time, unit: &Unit) -> Decimal { fn extract_from_timestamp(timestamp: Timestamp, unit: &Unit) -> Decimal { match unit { Epoch => { - let epoch = timestamp.0.timestamp_micros(); + let epoch = timestamp.0.and_utc().timestamp_micros(); Decimal::from_i128_with_scale(epoch as i128, 6) } Julian => { - let epoch = Decimal::from_i128_with_scale(timestamp.0.timestamp_micros() as i128, 6); + let epoch = + Decimal::from_i128_with_scale(timestamp.0.and_utc().timestamp_micros() as i128, 6); epoch / (24 * 60 * 60).into() + 2_440_588.into() } _ if unit.is_date_unit() => extract_from_datelike(timestamp.0.date(), *unit), diff --git a/src/expr/impl/src/scalar/format.rs b/src/expr/impl/src/scalar/format.rs index 50195638e4d0e..0df53bcc277cd 100644 --- a/src/expr/impl/src/scalar/format.rs +++ b/src/expr/impl/src/scalar/format.rs @@ -18,7 +18,6 @@ use std::str::FromStr; use risingwave_common::row::Row; use risingwave_common::types::{ScalarRefImpl, ToText}; use risingwave_expr::{function, ExprError, Result}; -use thiserror_ext::AsReport; use super::string::quote_ident; diff --git a/src/expr/impl/src/scalar/jsonb_record.rs b/src/expr/impl/src/scalar/jsonb_record.rs index fcc9606897ab7..b1d399d35a5f9 100644 --- a/src/expr/impl/src/scalar/jsonb_record.rs +++ b/src/expr/impl/src/scalar/jsonb_record.rs @@ -41,6 +41,14 @@ use risingwave_expr::{function, ExprError, Result}; /// )).*; /// ---- /// 1 {2,"a b"} (4,"a b c") +/// +/// query II +/// select * from jsonb_populate_record( +/// null::struct, +/// '{"a": 1, "b": 2}' +/// ); +/// ---- +/// 1 2 /// ``` #[function("jsonb_populate_record(struct, jsonb) -> struct")] fn jsonb_populate_record( diff --git a/src/expr/impl/src/scalar/jsonb_set.rs b/src/expr/impl/src/scalar/jsonb_set.rs new file mode 100644 index 0000000000000..e3efefb05d416 --- /dev/null +++ b/src/expr/impl/src/scalar/jsonb_set.rs @@ -0,0 +1,186 @@ +// Copyright 2024 RisingWave Labs +// +// 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 jsonbb::ValueRef; +use risingwave_common::types::{JsonbRef, JsonbVal, ListRef}; +use risingwave_expr::{function, ExprError, Result}; + +/// Returns `target` with the item designated by `path` replaced by `new_value`, or with `new_value` +/// added if `create_if_missing` is true (which is the default) and the item designated by path does +/// not exist. All earlier steps in the path must exist, or the `target` is returned unchanged. As +/// with the path oriented operators, negative integers that appear in the path count from the end +/// of JSON arrays. +/// +/// If the last path step is an array index that is out of range, and `create_if_missing` is true, +/// the new value is added at the beginning of the array if the index is negative, or at the end of +/// the array if it is positive. +/// +/// # Examples +/// +/// ```slt +/// query T +/// SELECT jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}', '[2,3,4]', false); +/// ---- +/// [{"f1": [2, 3, 4], "f2": null}, 2, null, 3] +/// +/// query T +/// SELECT jsonb_set('[{"f1":1,"f2":null},2]', '{0,f3}', '[2,3,4]'); +/// ---- +/// [{"f1": 1, "f2": null, "f3": [2, 3, 4]}, 2] +/// ``` +#[function("jsonb_set(jsonb, varchar[], jsonb, boolean) -> jsonb")] +fn jsonb_set4( + target: JsonbRef<'_>, + path: ListRef<'_>, + new_value: JsonbRef<'_>, + create_if_missing: bool, +) -> Result { + if target.is_scalar() { + return Err(ExprError::InvalidParam { + name: "jsonb", + reason: "cannot set path in scalar".into(), + }); + } + let target: ValueRef<'_> = target.into(); + let new_value: ValueRef<'_> = new_value.into(); + let mut builder = jsonbb::Builder::>::with_capacity(target.capacity()); + jsonbb_set_path(target, path, 0, new_value, create_if_missing, &mut builder)?; + Ok(JsonbVal::from(builder.finish())) +} + +#[function("jsonb_set(jsonb, varchar[], jsonb) -> jsonb")] +fn jsonb_set3( + target: JsonbRef<'_>, + path: ListRef<'_>, + new_value: JsonbRef<'_>, +) -> Result { + jsonb_set4(target, path, new_value, true) +} + +/// Recursively set `path[i..]` in `target` to `new_value` and write the result to `builder`. +/// +/// Panics if `i` is out of bounds. +fn jsonbb_set_path( + target: ValueRef<'_>, + path: ListRef<'_>, + i: usize, + new_value: ValueRef<'_>, + create_if_missing: bool, + builder: &mut jsonbb::Builder, +) -> Result<()> { + let last_step = i == path.len() - 1; + match target { + ValueRef::Object(obj) => { + let key = path + .get(i) + .unwrap() + .ok_or_else(|| ExprError::InvalidParam { + name: "path", + reason: format!("path element at position {} is null", i + 1).into(), + })? + .into_utf8(); + builder.begin_object(); + for (k, v) in obj.iter() { + builder.add_string(k); + if k != key { + builder.add_value(v); + } else if last_step { + builder.add_value(new_value); + } else { + // recursively set path[i+1..] in v + jsonbb_set_path(v, path, i + 1, new_value, create_if_missing, builder)?; + } + } + if create_if_missing && last_step && !obj.contains_key(key) { + builder.add_string(key); + builder.add_value(new_value); + } + builder.end_object(); + Ok(()) + } + ValueRef::Array(array) => { + let key = path + .get(i) + .unwrap() + .ok_or_else(|| ExprError::InvalidParam { + name: "path", + reason: format!("path element at position {} is null", i + 1).into(), + })? + .into_utf8(); + let idx = key.parse::().map_err(|_| ExprError::InvalidParam { + name: "path", + reason: format!( + "path element at position {} is not an integer: \"{}\"", + i + 1, + key + ) + .into(), + })?; + let Some(idx) = normalize_array_index(array.len(), idx) else { + // out of bounds index + if create_if_missing { + builder.begin_array(); + // the new value is added at the beginning of the array if the index is negative + if idx < 0 { + builder.add_value(new_value); + } + for v in array.iter() { + builder.add_value(v); + } + // or at the end of the array if it is positive. + if idx >= 0 { + builder.add_value(new_value); + } + builder.end_array(); + } else { + builder.add_value(target); + } + return Ok(()); + }; + builder.begin_array(); + for (j, v) in array.iter().enumerate() { + if j != idx { + builder.add_value(v); + continue; + } + if last_step { + builder.add_value(new_value); + } else { + // recursively set path[i+1..] in v + jsonbb_set_path(v, path, i + 1, new_value, create_if_missing, builder)?; + } + } + builder.end_array(); + Ok(()) + } + _ => { + builder.add_value(target); + Ok(()) + } + } +} + +/// Normalizes an array index to `0..len`. +/// Negative indices count from the end. i.e. `-len..0 => 0..len`. +/// Returns `None` if index is out of bounds. +fn normalize_array_index(len: usize, index: i32) -> Option { + if index < -(len as i32) || index >= (len as i32) { + return None; + } + if index >= 0 { + Some(index as usize) + } else { + Some((len as i32 + index) as usize) + } +} diff --git a/src/expr/impl/src/scalar/mod.rs b/src/expr/impl/src/scalar/mod.rs index edbaaf4de01ab..fbf9b512ea86d 100644 --- a/src/expr/impl/src/scalar/mod.rs +++ b/src/expr/impl/src/scalar/mod.rs @@ -58,6 +58,7 @@ mod jsonb_info; mod jsonb_object; mod jsonb_path; mod jsonb_record; +mod jsonb_set; mod length; mod lower; mod make_time; @@ -83,6 +84,7 @@ pub use to_jsonb::*; mod encrypt; mod external; mod inet; +mod test_license; mod to_timestamp; mod translate; mod trigonometric; diff --git a/src/expr/impl/src/scalar/proctime.rs b/src/expr/impl/src/scalar/proctime.rs index 2e96e602633cb..2c3c2c651c3df 100644 --- a/src/expr/impl/src/scalar/proctime.rs +++ b/src/expr/impl/src/scalar/proctime.rs @@ -25,7 +25,6 @@ fn proctime() -> Result { #[cfg(test)] mod tests { - use risingwave_common::types::Timestamptz; use risingwave_common::util::epoch::{Epoch, EpochPair}; use super::*; diff --git a/src/expr/impl/src/scalar/round.rs b/src/expr/impl/src/scalar/round.rs index a43dd425e13c3..90c9512c6c9b2 100644 --- a/src/expr/impl/src/scalar/round.rs +++ b/src/expr/impl/src/scalar/round.rs @@ -73,9 +73,6 @@ pub fn round_decimal(input: Decimal) -> Decimal { mod tests { use std::str::FromStr; - use risingwave_common::types::{Decimal, F64}; - - use super::ceil_f64; use crate::scalar::round::*; fn do_test(input: &str, digits: i32, expected_output: Option<&str>) { diff --git a/src/expr/impl/src/scalar/string.rs b/src/expr/impl/src/scalar/string.rs index 700fe8e632b35..32ba0787d75b7 100644 --- a/src/expr/impl/src/scalar/string.rs +++ b/src/expr/impl/src/scalar/string.rs @@ -472,6 +472,74 @@ pub fn right(s: &str, n: i32, writer: &mut impl Write) { .for_each(|c| writer.write_char(c).unwrap()); } +/// `quote_literal(string text)` +/// `quote_literal(value anyelement)` +/// +/// Return the given string suitably quoted to be used as a string literal in an SQL statement +/// string. Embedded single-quotes and backslashes are properly doubled. +/// Note that `quote_literal` returns null on null input; if the argument might be null, +/// `quote_nullable` is often more suitable. +/// +/// # Example +/// +/// Note that the quotes are part of the output string. +/// +/// ```slt +/// query T +/// select quote_literal(E'O\'Reilly') +/// ---- +/// 'O''Reilly' +/// +/// query T +/// select quote_literal(E'C:\\Windows\\') +/// ---- +/// E'C:\\Windows\\' +/// +/// query T +/// select quote_literal(42.5) +/// ---- +/// '42.5' +/// +/// query T +/// select quote_literal('hello'::bytea); +/// ---- +/// E'\\x68656c6c6f' +/// +/// query T +/// select quote_literal('{"hello":"world","foo":233}'::jsonb); +/// ---- +/// '{"foo": 233, "hello": "world"}' +/// ``` +#[function("quote_literal(varchar) -> varchar")] +pub fn quote_literal(s: &str, writer: &mut impl Write) { + if s.contains('\\') { + // use escape format: E'...' + write!(writer, "E").unwrap(); + } + write!(writer, "'").unwrap(); + for c in s.chars() { + match c { + '\'' => write!(writer, "''").unwrap(), + '\\' => write!(writer, "\\\\").unwrap(), + _ => write!(writer, "{}", c).unwrap(), + } + } + write!(writer, "'").unwrap(); +} + +/// `quote_nullable(string text)` +/// +/// Return the given string suitably quoted to be used as a string literal in an SQL statement +/// string; or, if the argument is null, return NULL. +/// Embedded single-quotes and backslashes are properly doubled. +#[function("quote_nullable(varchar) -> varchar")] +pub fn quote_nullable(s: Option<&str>, writer: &mut impl Write) { + match s { + Some(s) => quote_literal(s, writer), + None => write!(writer, "NULL").unwrap(), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/expr/udf/src/lib.rs b/src/expr/impl/src/scalar/test_license.rs similarity index 58% rename from src/expr/udf/src/lib.rs rename to src/expr/impl/src/scalar/test_license.rs index ddd8cf1bdeab9..de1afd5ed616b 100644 --- a/src/expr/udf/src/lib.rs +++ b/src/expr/impl/src/scalar/test_license.rs @@ -12,13 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![feature(error_generic_member_access)] -#![feature(lazy_cell)] +use risingwave_common::license::Feature; +use risingwave_expr::{function, ExprError, Result}; -mod error; -mod external; -pub mod metrics; - -pub use error::{Error, Result}; -pub use external::ArrowFlightUdfClient; -pub use metrics::GLOBAL_METRICS; +/// A function that checks if the `TestPaid` feature is available. +/// +/// It's mainly for testing purposes only. +#[function("test_paid_tier() -> boolean")] +pub fn test_paid_tier() -> Result { + Feature::TestPaid + .check_available() + .map_err(|e| ExprError::Internal(anyhow::Error::from(e)))?; + Ok(true) +} diff --git a/src/expr/impl/src/scalar/to_char.rs b/src/expr/impl/src/scalar/to_char.rs index eef011d29ef00..7c9e859675d03 100644 --- a/src/expr/impl/src/scalar/to_char.rs +++ b/src/expr/impl/src/scalar/to_char.rs @@ -351,6 +351,7 @@ fn format_inner(w: &mut impl Write, interval: Interval, item: &Item<'_>) -> Resu | WeekFromMon | IsoYearDiv100 | Timestamp | YearDiv100 | Internal(_) => { unreachable!() } + _ => unreachable!(), } Ok(()) } @@ -395,6 +396,7 @@ fn format_inner(w: &mut impl Write, interval: Interval, item: &Item<'_>) -> Resu | Nanosecond9 | RFC2822 | RFC3339 => unreachable!(), + _ => unreachable!(), } } Item::Error => Err(invalid_pattern_err()), diff --git a/src/expr/impl/src/scalar/trigonometric.rs b/src/expr/impl/src/scalar/trigonometric.rs index 705ee8f3eeba2..20e2b3523716b 100644 --- a/src/expr/impl/src/scalar/trigonometric.rs +++ b/src/expr/impl/src/scalar/trigonometric.rs @@ -362,7 +362,7 @@ pub fn radians_f64(input: F64) -> F64 { mod tests { use std::f64::consts::PI; - use risingwave_common::types::{FloatExt, F64}; + use risingwave_common::types::FloatExt; use crate::scalar::trigonometric::*; diff --git a/src/expr/impl/src/scalar/tumble.rs b/src/expr/impl/src/scalar/tumble.rs index 3e383e59236ee..bf6f32253207a 100644 --- a/src/expr/impl/src/scalar/tumble.rs +++ b/src/expr/impl/src/scalar/tumble.rs @@ -38,7 +38,7 @@ pub fn tumble_start_date(timestamp: Date, window_size: Interval) -> Result timestamp")] pub fn tumble_start_date_time(timestamp: Timestamp, window_size: Interval) -> Result { - let timestamp_micro_second = timestamp.0.timestamp_micros(); + let timestamp_micro_second = timestamp.0.and_utc().timestamp_micros(); let window_start_micro_second = get_window_start(timestamp_micro_second, window_size)?; Ok(Timestamp::from_timestamp_uncheck( window_start_micro_second / 1_000_000, @@ -72,7 +72,7 @@ pub fn tumble_start_offset_date_time( window_size: Interval, offset: Interval, ) -> Result { - let timestamp_micro_second = time.0.timestamp_micros(); + let timestamp_micro_second = time.0.and_utc().timestamp_micros(); let window_start_micro_second = get_window_start_with_offset(timestamp_micro_second, window_size, offset)?; diff --git a/src/expr/impl/src/table_function/generate_series.rs b/src/expr/impl/src/table_function/generate_series.rs index d7d4bc1db37d7..91cd748a47922 100644 --- a/src/expr/impl/src/table_function/generate_series.rs +++ b/src/expr/impl/src/table_function/generate_series.rs @@ -18,7 +18,7 @@ use risingwave_expr::{function, ExprError, Result}; #[function("generate_series(int4, int4) -> setof int4")] #[function("generate_series(int8, int8) -> setof int8")] -fn generate_series(start: T, stop: T) -> Result>> +fn generate_series(start: T, stop: T) -> Result> where T: CheckedAdd + PartialOrd + Copy + One + IsNegative, { @@ -26,10 +26,7 @@ where } #[function("generate_series(decimal, decimal) -> setof decimal")] -fn generate_series_decimal( - start: Decimal, - stop: Decimal, -) -> Result>> +fn generate_series_decimal(start: Decimal, stop: Decimal) -> Result> where { validate_range_parameters(start, stop, Decimal::one())?; @@ -39,7 +36,7 @@ where #[function("generate_series(int4, int4, int4) -> setof int4")] #[function("generate_series(int8, int8, int8) -> setof int8")] #[function("generate_series(timestamp, timestamp, interval) -> setof timestamp")] -fn generate_series_step(start: T, stop: T, step: S) -> Result>> +fn generate_series_step(start: T, stop: T, step: S) -> Result> where T: CheckedAdd + PartialOrd + Copy, S: IsNegative + Copy, @@ -52,14 +49,14 @@ fn generate_series_step_decimal( start: Decimal, stop: Decimal, step: Decimal, -) -> Result>> { +) -> Result> { validate_range_parameters(start, stop, step)?; range_generic::<_, _, true>(start, stop, step) } #[function("range(int4, int4) -> setof int4")] #[function("range(int8, int8) -> setof int8")] -fn range(start: T, stop: T) -> Result>> +fn range(start: T, stop: T) -> Result> where T: CheckedAdd + PartialOrd + Copy + One + IsNegative, { @@ -67,7 +64,7 @@ where } #[function("range(decimal, decimal) -> setof decimal")] -fn range_decimal(start: Decimal, stop: Decimal) -> Result>> +fn range_decimal(start: Decimal, stop: Decimal) -> Result> where { validate_range_parameters(start, stop, Decimal::one())?; @@ -77,7 +74,7 @@ where #[function("range(int4, int4, int4) -> setof int4")] #[function("range(int8, int8, int8) -> setof int8")] #[function("range(timestamp, timestamp, interval) -> setof timestamp")] -fn range_step(start: T, stop: T, step: S) -> Result>> +fn range_step(start: T, stop: T, step: S) -> Result> where T: CheckedAdd + PartialOrd + Copy, S: IsNegative + Copy, @@ -90,7 +87,7 @@ fn range_step_decimal( start: Decimal, stop: Decimal, step: Decimal, -) -> Result>> { +) -> Result> { validate_range_parameters(start, stop, step)?; range_generic::<_, _, false>(start, stop, step) } @@ -100,7 +97,7 @@ fn range_generic( start: T, stop: T, step: S, -) -> Result>> +) -> Result> where T: CheckedAdd + PartialOrd + Copy, S: IsNegative + Copy, @@ -113,21 +110,22 @@ where } let mut cur = start; let neg = step.is_negative(); - let mut next = move || { + let next = move || { match (INCLUSIVE, neg) { - (true, true) if cur < stop => return Ok(None), - (true, false) if cur > stop => return Ok(None), - (false, true) if cur <= stop => return Ok(None), - (false, false) if cur >= stop => return Ok(None), + (true, true) if cur < stop => return None, + (true, false) if cur > stop => return None, + (false, true) if cur <= stop => return None, + (false, false) if cur >= stop => return None, _ => {} }; let ret = cur; - cur = cur.checked_add(step).ok_or(ExprError::NumericOutOfRange)?; - Ok(Some(ret)) + cur = cur.checked_add(step)?; + Some(ret) }; - Ok(std::iter::from_fn(move || next().transpose())) + Ok(std::iter::from_fn(next)) } +/// Validate decimals can not be `NaN` or `infinity`. #[inline] fn validate_range_parameters(start: Decimal, stop: Decimal, step: Decimal) -> Result<()> { validate_decimal(start, "start")?; @@ -160,8 +158,7 @@ mod tests { use risingwave_common::types::test_utils::IntervalTestExt; use risingwave_common::types::{DataType, Decimal, Interval, ScalarImpl, Timestamp}; use risingwave_expr::expr::{BoxedExpression, ExpressionBoxExt, LiteralExpression}; - use risingwave_expr::table_function::build; - use risingwave_expr::ExprError; + use risingwave_expr::table_function::{build, check_error}; use risingwave_pb::expr::table_function::PbType; const CHUNK_SIZE: usize = 1024; @@ -309,40 +306,27 @@ mod tests { #[tokio::test] async fn test_generate_series_decimal() { - let start = Decimal::from_str("1").unwrap(); - let start_inf = Decimal::from_str("infinity").unwrap(); - let stop = Decimal::from_str("5").unwrap(); - let stop_inf = Decimal::from_str("-infinity").unwrap(); - - let step = Decimal::from_str("1").unwrap(); - let step_nan = Decimal::from_str("nan").unwrap(); - let step_inf = Decimal::from_str("infinity").unwrap(); - generate_series_decimal(start, stop, step, true).await; - generate_series_decimal(start_inf, stop, step, false).await; - generate_series_decimal(start_inf, stop_inf, step, false).await; - generate_series_decimal(start, stop_inf, step, false).await; - generate_series_decimal(start, stop, step_nan, false).await; - generate_series_decimal(start, stop, step_inf, false).await; - generate_series_decimal(start, stop_inf, step_nan, false).await; + generate_series_decimal("1", "5", "1", true).await; + generate_series_decimal("inf", "5", "1", false).await; + generate_series_decimal("inf", "-inf", "1", false).await; + generate_series_decimal("1", "-inf", "1", false).await; + generate_series_decimal("1", "5", "nan", false).await; + generate_series_decimal("1", "5", "inf", false).await; + generate_series_decimal("1", "-inf", "nan", false).await; } - async fn generate_series_decimal( - start: Decimal, - stop: Decimal, - step: Decimal, - expect_ok: bool, - ) { - fn literal(ty: DataType, v: ScalarImpl) -> BoxedExpression { - LiteralExpression::new(ty, Some(v)).boxed() + async fn generate_series_decimal(start: &str, stop: &str, step: &str, expect_ok: bool) { + fn decimal_literal(v: Decimal) -> BoxedExpression { + LiteralExpression::new(DataType::Decimal, Some(v.into())).boxed() } let function = build( PbType::GenerateSeries, DataType::Decimal, CHUNK_SIZE, vec![ - literal(DataType::Decimal, start.into()), - literal(DataType::Decimal, stop.into()), - literal(DataType::Decimal, step.into()), + decimal_literal(start.parse().unwrap()), + decimal_literal(stop.parse().unwrap()), + decimal_literal(step.parse().unwrap()), ], ) .unwrap(); @@ -350,17 +334,13 @@ mod tests { let dummy_chunk = DataChunk::new_dummy(1); let mut output = function.eval(&dummy_chunk).await; while let Some(res) = output.next().await { - match res { - Ok(_) => { - assert!(expect_ok); - } - Err(ExprError::InvalidParam { .. }) => { - assert!(!expect_ok); - } - Err(_) => { - unreachable!(); - } - } + let chunk = res.unwrap(); + let error = check_error(&chunk); + assert_eq!( + error.is_ok(), + expect_ok, + "generate_series({start}, {stop}, {step})" + ); } } } diff --git a/src/expr/impl/src/table_function/mod.rs b/src/expr/impl/src/table_function/mod.rs index 4a52ec5bd1f41..dfafb6721e866 100644 --- a/src/expr/impl/src/table_function/mod.rs +++ b/src/expr/impl/src/table_function/mod.rs @@ -16,5 +16,6 @@ mod generate_series; mod generate_subscripts; mod jsonb; mod pg_expandarray; +mod pg_get_keywords; mod regexp_matches; mod unnest; diff --git a/src/expr/impl/src/table_function/pg_get_keywords.rs b/src/expr/impl/src/table_function/pg_get_keywords.rs new file mode 100644 index 0000000000000..bd18660872720 --- /dev/null +++ b/src/expr/impl/src/table_function/pg_get_keywords.rs @@ -0,0 +1,58 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_expr::function; +use risingwave_sqlparser::keywords::{ + ALL_KEYWORDS_INDEX, RESERVED_FOR_COLUMN_ALIAS, RESERVED_FOR_COLUMN_OR_TABLE_NAME, +}; + +/// Returns a set of records describing the SQL keywords recognized by the server. +/// +/// The word column contains the keyword. +/// +/// The catcode column contains a category code: +/// - U for an unreserved keyword +/// - C for a keyword that can be a column name +/// - T for a keyword that can be a type or function name +/// - R for a fully reserved keyword. +/// +/// The catdesc column contains a possibly-localized string describing the keyword's category. +/// +/// ```slt +/// query TTT +/// select * from pg_get_keywords() where word = 'add'; +/// ---- +/// add U unreserved +/// ``` +#[function("pg_get_keywords() -> setof struct")] +fn pg_get_keywords() -> impl Iterator, &'static str, &'static str)> { + ALL_KEYWORDS_INDEX.iter().map(|keyword| { + // FIXME: The current category is not correct. Many are different from the PostgreSQL. + let catcode = if !RESERVED_FOR_COLUMN_OR_TABLE_NAME.contains(keyword) { + "U" + } else if !RESERVED_FOR_COLUMN_ALIAS.contains(keyword) { + "C" + } else { + "R" + }; + let catdesc = match catcode { + "U" => "unreserved", + "C" => "unreserved (cannot be function or type name)", + "T" => "reserved (can be function or type name)", + "R" => "reserved", + _ => unreachable!(), + }; + (keyword.to_string().to_lowercase().into(), catcode, catdesc) + }) +} diff --git a/src/expr/impl/src/udf/deno.rs b/src/expr/impl/src/udf/deno.rs new file mode 100644 index 0000000000000..8c0c0a4674a8c --- /dev/null +++ b/src/expr/impl/src/udf/deno.rs @@ -0,0 +1,127 @@ +// Copyright 2024 RisingWave Labs +// +// 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 anyhow::bail; +use arrow_udf_js_deno::{CallMode, Runtime}; +use futures_async_stream::try_stream; +use risingwave_common::array::arrow::{ToArrow, UdfArrowConvert}; + +use super::*; + +#[linkme::distributed_slice(UDF_IMPLS)] +static DENO: UdfImplDescriptor = UdfImplDescriptor { + match_fn: |language, runtime, _link| language == "javascript" && runtime == Some("deno"), + create_fn: |opts| { + let mut body = None; + let mut compressed_binary = None; + let identifier = opts.name.to_string(); + match (opts.using_link, opts.using_base64_decoded, opts.as_) { + (None, None, Some(as_)) => body = Some(as_.to_string()), + (Some(link), None, None) => { + let bytes = read_file_from_link(link)?; + compressed_binary = Some(zstd::stream::encode_all(bytes.as_slice(), 0)?); + } + (None, Some(bytes), None) => { + compressed_binary = Some(zstd::stream::encode_all(bytes, 0)?); + } + (None, None, None) => bail!("Either USING or AS must be specified"), + _ => bail!("Both USING and AS cannot be specified"), + } + Ok(CreateFunctionOutput { + identifier, + body, + compressed_binary, + }) + }, + build_fn: |opts| { + let runtime = Runtime::new(); + let body = match (opts.body, opts.compressed_binary) { + (Some(body), _) => body.to_string(), + (_, Some(compressed_binary)) => { + let binary = zstd::stream::decode_all(compressed_binary) + .context("failed to decompress binary")?; + String::from_utf8(binary).context("failed to decode binary")? + } + _ => bail!("UDF body or compressed binary is required for deno UDF"), + }; + + let body = format!( + "export {} {}({}) {{ {} }}", + match opts.function_type { + Some("sync") => "function", + Some("async") => "async function", + Some("generator") => "function*", + Some("async_generator") => "async function*", + _ if opts.kind.is_table() => "function*", + _ => "function", + }, + opts.identifier, + opts.arg_names.join(","), + body + ); + + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(runtime.add_function( + opts.identifier, + UdfArrowConvert::default().to_arrow_field("", opts.return_type)?, + CallMode::CalledOnNullInput, + &body, + )) + })?; + + Ok(Box::new(DenoFunction { + runtime, + identifier: opts.identifier.to_string(), + })) + }, +}; + +#[derive(Debug)] +struct DenoFunction { + runtime: Arc, + identifier: String, +} + +#[async_trait::async_trait] +impl UdfImpl for DenoFunction { + async fn call(&self, input: &RecordBatch) -> Result { + // FIXME(runji): make the future Send + tokio::task::block_in_place(|| { + tokio::runtime::Handle::current() + .block_on(self.runtime.call(&self.identifier, input.clone())) + }) + } + + async fn call_table_function<'a>( + &'a self, + input: &'a RecordBatch, + ) -> Result>> { + Ok(self.call_table_function_inner(input.clone())) + } +} + +impl DenoFunction { + #[try_stream(boxed, ok = RecordBatch, error = anyhow::Error)] + async fn call_table_function_inner<'a>(&'a self, input: RecordBatch) { + let mut stream = self + .runtime + .call_table_function(&self.identifier, input, 1024) + .await?; + while let Some(batch) = stream.next().await { + yield batch?; + } + } +} diff --git a/src/expr/impl/src/udf/external.rs b/src/expr/impl/src/udf/external.rs new file mode 100644 index 0000000000000..5c400df26c179 --- /dev/null +++ b/src/expr/impl/src/udf/external.rs @@ -0,0 +1,289 @@ +// Copyright 2024 RisingWave Labs +// +// 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::HashMap; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::{Arc, LazyLock, Weak}; +use std::time::Duration; + +use anyhow::bail; +use arrow_schema::Fields; +use arrow_udf_flight::Client; +use futures_util::{StreamExt, TryStreamExt}; +use ginepro::{LoadBalancedChannel, ResolutionStrategy}; +use risingwave_common::array::arrow::{ToArrow, UdfArrowConvert}; +use risingwave_common::util::addr::HostAddr; +use thiserror_ext::AsReport; + +use super::*; + +#[linkme::distributed_slice(UDF_IMPLS)] +static EXTERNAL: UdfImplDescriptor = UdfImplDescriptor { + match_fn: |language, _runtime, link| { + link.is_some() && matches!(language, "python" | "java" | "") + }, + create_fn: |opts| { + let link = opts.using_link.context("USING LINK must be specified")?; + let identifier = opts.as_.context("AS must be specified")?.to_string(); + + // check UDF server + let client = get_or_create_flight_client(link)?; + let convert = UdfArrowConvert { + legacy: client.protocol_version() == 1, + }; + // A helper function to create a unnamed field from data type. + let to_field = |data_type| convert.to_arrow_field("", data_type); + let args = arrow_schema::Schema::new( + opts.arg_types + .iter() + .map(to_field) + .try_collect::()?, + ); + let returns = arrow_schema::Schema::new(if opts.kind.is_table() { + vec![ + arrow_schema::Field::new("row", arrow_schema::DataType::Int32, true), + to_field(opts.return_type)?, + ] + } else { + vec![to_field(opts.return_type)?] + }); + let function = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(client.get(&identifier)) + }) + .context("failed to check UDF signature")?; + if !data_types_match(&function.args, &args) { + bail!( + "argument type mismatch, expect: {:?}, actual: {:?}", + args, + function.args, + ); + } + if !data_types_match(&function.returns, &returns) { + bail!( + "return type mismatch, expect: {:?}, actual: {:?}", + returns, + function.returns, + ); + } + Ok(CreateFunctionOutput { + identifier, + body: None, + compressed_binary: None, + }) + }, + build_fn: |opts| { + let link = opts.link.context("link is required")?; + let client = get_or_create_flight_client(link)?; + Ok(Box::new(ExternalFunction { + identifier: opts.identifier.to_string(), + client, + disable_retry_count: AtomicU8::new(INITIAL_RETRY_COUNT), + always_retry_on_network_error: opts.always_retry_on_network_error, + })) + }, +}; + +#[derive(Debug)] +struct ExternalFunction { + identifier: String, + client: Arc, + /// Number of remaining successful calls until retry is enabled. + /// This parameter is designed to prevent continuous retry on every call, which would increase delay. + /// Logic: + /// It resets to `INITIAL_RETRY_COUNT` after a single failure and then decrements with each call, enabling retry when it reaches zero. + /// If non-zero, we will not retry on connection errors to prevent blocking the stream. + /// On each connection error, the count will be reset to `INITIAL_RETRY_COUNT`. + /// On each successful call, the count will be decreased by 1. + /// Link: + /// See . + disable_retry_count: AtomicU8, + /// Always retry. Overrides `disable_retry_count`. + always_retry_on_network_error: bool, +} + +const INITIAL_RETRY_COUNT: u8 = 16; + +#[async_trait::async_trait] +impl UdfImpl for ExternalFunction { + fn is_legacy(&self) -> bool { + // see for details + self.client.protocol_version() == 1 + } + + async fn call(&self, input: &RecordBatch) -> Result { + let disable_retry_count = self.disable_retry_count.load(Ordering::Relaxed); + let result = if self.always_retry_on_network_error { + self.call_with_always_retry_on_network_error(input).await + } else { + let result = if disable_retry_count != 0 { + self.client.call(&self.identifier, input).await + } else { + self.call_with_retry(input).await + }; + let disable_retry_count = self.disable_retry_count.load(Ordering::Relaxed); + let connection_error = matches!(&result, Err(e) if is_connection_error(e)); + if connection_error && disable_retry_count != INITIAL_RETRY_COUNT { + // reset count on connection error + self.disable_retry_count + .store(INITIAL_RETRY_COUNT, Ordering::Relaxed); + } else if !connection_error && disable_retry_count != 0 { + // decrease count on success, ignore if exchange failed + _ = self.disable_retry_count.compare_exchange( + disable_retry_count, + disable_retry_count - 1, + Ordering::Relaxed, + Ordering::Relaxed, + ); + } + result + }; + result.map_err(|e| e.into()) + } + + async fn call_table_function<'a>( + &'a self, + input: &'a RecordBatch, + ) -> Result>> { + let stream = self + .client + .call_table_function(&self.identifier, input) + .await?; + Ok(stream.map_err(|e| e.into()).boxed()) + } +} + +/// Get or create a client for the given UDF service. +/// +/// There is a global cache for clients, so that we can reuse the same client for the same service. +fn get_or_create_flight_client(link: &str) -> Result> { + static CLIENTS: LazyLock>>> = + LazyLock::new(Default::default); + let mut clients = CLIENTS.lock().unwrap(); + if let Some(client) = clients.get(link).and_then(|c| c.upgrade()) { + // reuse existing client + Ok(client) + } else { + // create new client + let client = Arc::new(tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(async { + let channel = connect_tonic(link).await?; + Ok(Client::new(channel).await?) as Result<_> + }) + })?); + clients.insert(link.to_owned(), Arc::downgrade(&client)); + Ok(client) + } +} + +/// Connect to a UDF service and return a tonic `Channel`. +async fn connect_tonic(mut addr: &str) -> Result { + // Interval between two successive probes of the UDF DNS. + const DNS_PROBE_INTERVAL_SECS: u64 = 5; + // Timeout duration for performing an eager DNS resolution. + const EAGER_DNS_RESOLVE_TIMEOUT_SECS: u64 = 5; + const REQUEST_TIMEOUT_SECS: u64 = 5; + const CONNECT_TIMEOUT_SECS: u64 = 5; + + if let Some(s) = addr.strip_prefix("http://") { + addr = s; + } + if let Some(s) = addr.strip_prefix("https://") { + addr = s; + } + let host_addr = addr.parse::()?; + let channel = LoadBalancedChannel::builder((host_addr.host.clone(), host_addr.port)) + .dns_probe_interval(std::time::Duration::from_secs(DNS_PROBE_INTERVAL_SECS)) + .timeout(Duration::from_secs(REQUEST_TIMEOUT_SECS)) + .connect_timeout(Duration::from_secs(CONNECT_TIMEOUT_SECS)) + .resolution_strategy(ResolutionStrategy::Eager { + timeout: tokio::time::Duration::from_secs(EAGER_DNS_RESOLVE_TIMEOUT_SECS), + }) + .channel() + .await + .with_context(|| format!("failed to create LoadBalancedChannel, address: {host_addr}"))?; + Ok(channel.into()) +} + +impl ExternalFunction { + /// Call a function, retry up to 5 times / 3s if connection is broken. + async fn call_with_retry( + &self, + input: &RecordBatch, + ) -> Result { + let mut backoff = Duration::from_millis(100); + for i in 0..5 { + match self.client.call(&self.identifier, input).await { + Err(err) if is_connection_error(&err) && i != 4 => { + tracing::error!(?backoff, error = %err.as_report(), "UDF connection error. retry..."); + } + ret => return ret, + } + tokio::time::sleep(backoff).await; + backoff *= 2; + } + unreachable!() + } + + /// Always retry on connection error + async fn call_with_always_retry_on_network_error( + &self, + input: &RecordBatch, + ) -> Result { + let mut backoff = Duration::from_millis(100); + loop { + match self.client.call(&self.identifier, input).await { + Err(err) if is_tonic_error(&err) => { + tracing::error!(?backoff, error = %err.as_report(), "UDF tonic error. retry..."); + } + ret => { + if ret.is_err() { + tracing::error!(error = %ret.as_ref().unwrap_err().as_report(), "UDF error. exiting..."); + } + return ret; + } + } + tokio::time::sleep(backoff).await; + backoff *= 2; + } + } +} + +/// Returns true if the arrow flight error is caused by a connection error. +fn is_connection_error(err: &arrow_udf_flight::Error) -> bool { + match err { + // Connection refused + arrow_udf_flight::Error::Tonic(status) if status.code() == tonic::Code::Unavailable => true, + _ => false, + } +} + +fn is_tonic_error(err: &arrow_udf_flight::Error) -> bool { + matches!( + err, + arrow_udf_flight::Error::Tonic(_) + | arrow_udf_flight::Error::Flight(arrow_flight::error::FlightError::Tonic(_)) + ) +} + +/// Check if two list of data types match, ignoring field names. +fn data_types_match(a: &arrow_schema::Schema, b: &arrow_schema::Schema) -> bool { + if a.fields().len() != b.fields().len() { + return false; + } + #[allow(clippy::disallowed_methods)] + a.fields() + .iter() + .zip(b.fields()) + .all(|(a, b)| a.data_type().equals_datatype(b.data_type())) +} diff --git a/src/expr/impl/src/udf/mod.rs b/src/expr/impl/src/udf/mod.rs new file mode 100644 index 0000000000000..8ae9b6e98b69f --- /dev/null +++ b/src/expr/impl/src/udf/mod.rs @@ -0,0 +1,46 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +#![allow(dead_code, unused_imports)] + +// common imports for submodules +use anyhow::{Context as _, Result}; +use arrow_array::{ArrayRef, BooleanArray, RecordBatch}; +use futures_util::stream::BoxStream; +use risingwave_expr::sig::{ + CreateFunctionOptions, CreateFunctionOutput, UdfImpl, UdfImplDescriptor, UDF_IMPLS, +}; + +#[cfg(feature = "deno-udf")] +mod deno; +#[cfg(feature = "external-udf")] +#[cfg(not(madsim))] +mod external; +#[cfg(feature = "python-udf")] +mod python; +#[cfg(feature = "js-udf")] +mod quickjs; +#[cfg(feature = "wasm-udf")] +mod wasm; + +/// Download wasm binary from a link. +fn read_file_from_link(link: &str) -> Result> { + // currently only local file system is supported + let path = link + .strip_prefix("fs://") + .context("only 'fs://' is supported")?; + let content = + std::fs::read(path).context("failed to read wasm binary from local file system")?; + Ok(content) +} diff --git a/src/expr/impl/src/udf/python.rs b/src/expr/impl/src/udf/python.rs new file mode 100644 index 0000000000000..9ca4b2a5b2a24 --- /dev/null +++ b/src/expr/impl/src/udf/python.rs @@ -0,0 +1,97 @@ +// Copyright 2024 RisingWave Labs +// +// 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_schema::{DataType, Field}; +use arrow_udf_python::{CallMode, Runtime}; +use futures_util::StreamExt; +use risingwave_common::array::arrow::{ToArrow, UdfArrowConvert}; + +use super::*; + +#[linkme::distributed_slice(UDF_IMPLS)] +static PYTHON: UdfImplDescriptor = UdfImplDescriptor { + match_fn: |language, _runtime, link| language == "python" && link.is_none(), + create_fn: |opts| { + Ok(CreateFunctionOutput { + identifier: opts.name.to_string(), + body: Some(opts.as_.context("AS must be specified")?.to_string()), + compressed_binary: None, + }) + }, + build_fn: |opts| { + let mut runtime = Runtime::builder().sandboxed(true).build()?; + if opts.kind.is_aggregate() { + runtime.add_aggregate( + opts.identifier, + Field::new("state", DataType::Binary, true).with_metadata( + [("ARROW:extension:name".into(), "arrowudf.pickle".into())].into(), + ), + UdfArrowConvert::default().to_arrow_field("", opts.return_type)?, + CallMode::CalledOnNullInput, + opts.body.context("body is required")?, + )?; + } else { + runtime.add_function( + opts.identifier, + UdfArrowConvert::default().to_arrow_field("", opts.return_type)?, + CallMode::CalledOnNullInput, + opts.body.context("body is required")?, + )?; + } + Ok(Box::new(PythonFunction { + runtime, + identifier: opts.identifier.to_string(), + })) + }, +}; + +#[derive(Debug)] +struct PythonFunction { + runtime: Runtime, + identifier: String, +} + +#[async_trait::async_trait] +impl UdfImpl for PythonFunction { + async fn call(&self, input: &RecordBatch) -> Result { + self.runtime.call(&self.identifier, input) + } + + async fn call_table_function<'a>( + &'a self, + input: &'a RecordBatch, + ) -> Result>> { + self.runtime + .call_table_function(&self.identifier, input, 1024) + .map(|s| futures_util::stream::iter(s).boxed()) + } + + fn call_agg_create_state(&self) -> Result { + self.runtime.create_state(&self.identifier) + } + + fn call_agg_accumulate_or_retract( + &self, + state: &ArrayRef, + ops: &BooleanArray, + input: &RecordBatch, + ) -> Result { + self.runtime + .accumulate_or_retract(&self.identifier, state, ops, input) + } + + fn call_agg_finish(&self, state: &ArrayRef) -> Result { + self.runtime.finish(&self.identifier, state) + } +} diff --git a/src/expr/impl/src/udf/quickjs.rs b/src/expr/impl/src/udf/quickjs.rs new file mode 100644 index 0000000000000..7faa4dec8ae9f --- /dev/null +++ b/src/expr/impl/src/udf/quickjs.rs @@ -0,0 +1,110 @@ +// Copyright 2024 RisingWave Labs +// +// 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_schema::{DataType, Field}; +use arrow_udf_js::{CallMode, Runtime}; +use futures_util::StreamExt; +use risingwave_common::array::arrow::{ToArrow, UdfArrowConvert}; + +use super::*; + +#[linkme::distributed_slice(UDF_IMPLS)] +static QUICKJS: UdfImplDescriptor = UdfImplDescriptor { + match_fn: |language, runtime, _link| { + language == "javascript" && matches!(runtime, None | Some("quickjs")) + }, + create_fn: |opts| { + Ok(CreateFunctionOutput { + identifier: opts.name.to_string(), + body: Some(opts.as_.context("AS must be specified")?.to_string()), + compressed_binary: None, + }) + }, + build_fn: |opts| { + let mut runtime = Runtime::new()?; + if opts.kind.is_aggregate() { + runtime.add_aggregate( + opts.identifier, + Field::new("state", DataType::Binary, true).with_metadata( + [("ARROW:extension:name".into(), "arrowudf.json".into())].into(), + ), + UdfArrowConvert::default().to_arrow_field("", opts.return_type)?, + CallMode::CalledOnNullInput, + opts.body.context("body is required")?, + )?; + } else { + let body = format!( + "export function{} {}({}) {{ {} }}", + if opts.kind.is_table() { "*" } else { "" }, + opts.identifier, + opts.arg_names.join(","), + opts.body.context("body is required")?, + ); + runtime.add_function( + opts.identifier, + UdfArrowConvert::default().to_arrow_field("", opts.return_type)?, + CallMode::CalledOnNullInput, + &body, + )?; + } + Ok(Box::new(QuickJsFunction { + runtime, + identifier: opts.identifier.to_string(), + })) + }, +}; + +#[derive(Debug)] +struct QuickJsFunction { + runtime: Runtime, + identifier: String, +} + +#[async_trait::async_trait] +impl UdfImpl for QuickJsFunction { + async fn call(&self, input: &RecordBatch) -> Result { + self.runtime.call(&self.identifier, input) + } + + async fn call_table_function<'a>( + &'a self, + input: &'a RecordBatch, + ) -> Result>> { + self.runtime + .call_table_function(&self.identifier, input, 1024) + .map(|s| futures_util::stream::iter(s).boxed()) + } + + fn call_agg_create_state(&self) -> Result { + self.runtime.create_state(&self.identifier) + } + + fn call_agg_accumulate_or_retract( + &self, + state: &ArrayRef, + ops: &BooleanArray, + input: &RecordBatch, + ) -> Result { + self.runtime + .accumulate_or_retract(&self.identifier, state, ops, input) + } + + fn call_agg_finish(&self, state: &ArrayRef) -> Result { + self.runtime.finish(&self.identifier, state) + } + + fn memory_usage(&self) -> usize { + self.runtime.memory_usage().malloc_size as usize + } +} diff --git a/src/expr/impl/src/udf/wasm.rs b/src/expr/impl/src/udf/wasm.rs new file mode 100644 index 0000000000000..bd84cfa004326 --- /dev/null +++ b/src/expr/impl/src/udf/wasm.rs @@ -0,0 +1,283 @@ +// Copyright 2024 RisingWave Labs +// +// 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::borrow::Cow; +use std::sync::{Arc, LazyLock}; +use std::time::Duration; + +use anyhow::{anyhow, bail}; +use arrow_udf_wasm::Runtime; +use futures_util::StreamExt; +use itertools::Itertools; +use risingwave_common::types::DataType; +use risingwave_expr::sig::UdfOptions; + +use super::*; + +#[linkme::distributed_slice(UDF_IMPLS)] +static WASM: UdfImplDescriptor = UdfImplDescriptor { + match_fn: |language, _runtime, _link| language == "wasm", + create_fn: create_wasm, + build_fn: build, +}; + +#[linkme::distributed_slice(UDF_IMPLS)] +static RUST: UdfImplDescriptor = UdfImplDescriptor { + match_fn: |language, _runtime, _link| language == "rust", + create_fn: create_rust, + build_fn: build, +}; + +fn create_wasm(opts: CreateFunctionOptions<'_>) -> Result { + let wasm_binary: Cow<'_, [u8]> = if let Some(link) = opts.using_link { + read_file_from_link(link)?.into() + } else if let Some(bytes) = opts.using_base64_decoded { + bytes.into() + } else { + bail!("USING must be specified") + }; + let runtime = get_or_create_wasm_runtime(&wasm_binary)?; + if runtime.abi_version().0 <= 2 { + bail!("legacy arrow-udf is no longer supported. please update arrow-udf to 0.3+"); + } + let identifier_v1 = wasm_identifier_v1( + opts.name, + opts.arg_types, + opts.return_type, + opts.kind.is_table(), + ); + let identifier = find_wasm_identifier_v2(&runtime, &identifier_v1)?; + let compressed_binary = Some(zstd::stream::encode_all(&*wasm_binary, 0)?); + Ok(CreateFunctionOutput { + identifier, + body: None, + compressed_binary, + }) +} + +fn create_rust(opts: CreateFunctionOptions<'_>) -> Result { + if opts.using_link.is_some() { + bail!("USING is not supported for rust function"); + } + let identifier_v1 = wasm_identifier_v1( + opts.name, + opts.arg_types, + opts.return_type, + opts.kind.is_table(), + ); + // if the function returns a struct, users need to add `#[function]` macro by themselves. + // otherwise, we add it automatically. the code should start with `fn ...`. + let function_macro = if opts.return_type.is_struct() { + String::new() + } else { + format!("#[function(\"{}\")]", identifier_v1) + }; + let script = format!( + "use arrow_udf::{{function, types::*}};\n{}\n{}", + function_macro, + opts.as_.context("AS must be specified")? + ); + let body = Some(script.clone()); + + let wasm_binary = std::thread::spawn(move || { + let mut opts = arrow_udf_wasm::build::BuildOpts::default(); + opts.arrow_udf_version = Some("0.3".to_string()); + opts.script = script; + // use a fixed tempdir to reuse the build cache + opts.tempdir = Some(std::env::temp_dir().join("risingwave-rust-udf")); + + arrow_udf_wasm::build::build_with(&opts) + }) + .join() + .unwrap() + .context("failed to build rust function")?; + + let runtime = get_or_create_wasm_runtime(&wasm_binary)?; + let identifier = find_wasm_identifier_v2(&runtime, &identifier_v1)?; + + let compressed_binary = Some(zstd::stream::encode_all(wasm_binary.as_slice(), 0)?); + Ok(CreateFunctionOutput { + identifier, + body, + compressed_binary, + }) +} + +fn build(opts: UdfOptions<'_>) -> Result> { + let compressed_binary = opts + .compressed_binary + .context("compressed binary is required")?; + let wasm_binary = + zstd::stream::decode_all(compressed_binary).context("failed to decompress wasm binary")?; + let runtime = get_or_create_wasm_runtime(&wasm_binary)?; + Ok(Box::new(WasmFunction { + runtime, + identifier: opts.identifier.to_string(), + })) +} + +#[derive(Debug)] +struct WasmFunction { + runtime: Arc, + identifier: String, +} + +#[async_trait::async_trait] +impl UdfImpl for WasmFunction { + async fn call(&self, input: &RecordBatch) -> Result { + self.runtime.call(&self.identifier, input) + } + + async fn call_table_function<'a>( + &'a self, + input: &'a RecordBatch, + ) -> Result>> { + self.runtime + .call_table_function(&self.identifier, input) + .map(|s| futures_util::stream::iter(s).boxed()) + } + + fn is_legacy(&self) -> bool { + // see for details + self.runtime.abi_version().0 <= 2 + } +} + +/// Get or create a wasm runtime. +/// +/// Runtimes returned by this function are cached inside for at least 60 seconds. +/// Later calls with the same binary will reuse the same runtime. +fn get_or_create_wasm_runtime(binary: &[u8]) -> Result> { + static RUNTIMES: LazyLock>> = LazyLock::new(|| { + moka::sync::Cache::builder() + .time_to_idle(Duration::from_secs(60)) + .build() + }); + + let md5 = md5::compute(binary); + if let Some(runtime) = RUNTIMES.get(&md5) { + return Ok(runtime.clone()); + } + + let runtime = Arc::new(arrow_udf_wasm::Runtime::new(binary)?); + RUNTIMES.insert(md5, runtime.clone()); + Ok(runtime) +} + +/// Convert a v0.1 function identifier to v0.2 format. +/// +/// In arrow-udf v0.1 format, struct type is inline in the identifier. e.g. +/// +/// ```text +/// keyvalue(varchar,varchar)->struct +/// ``` +/// +/// However, since arrow-udf v0.2, struct type is no longer inline. +/// The above identifier is divided into a function and a type. +/// +/// ```text +/// keyvalue(varchar,varchar)->struct KeyValue +/// KeyValue=key:varchar,value:varchar +/// ``` +/// +/// For compatibility, we should call `find_wasm_identifier_v2` to +/// convert v0.1 identifiers to v0.2 format before looking up the function. +fn find_wasm_identifier_v2( + runtime: &arrow_udf_wasm::Runtime, + inlined_signature: &str, +) -> Result { + // Inline types in function signature. + // + // # Example + // + // ```text + // types = { "KeyValue": "key:varchar,value:varchar" } + // input = "keyvalue(varchar, varchar) -> struct KeyValue" + // output = "keyvalue(varchar, varchar) -> struct" + // ``` + let inline_types = |s: &str| -> String { + let mut inlined = s.to_string(); + // iteratively replace `struct Xxx` with `struct<...>` until no replacement is made. + loop { + let replaced = inlined.clone(); + for (k, v) in runtime.types() { + inlined = inlined.replace(&format!("struct {k}"), &format!("struct<{v}>")); + } + if replaced == inlined { + return inlined; + } + } + }; + // Function signature in arrow-udf is case sensitive. + // However, SQL identifiers are usually case insensitive and stored in lowercase. + // So we should convert the signature to lowercase before comparison. + let identifier = runtime + .functions() + .find(|f| inline_types(f).to_lowercase() == inlined_signature) + .ok_or_else(|| { + anyhow!( + "function not found in wasm binary: \"{}\"\nHINT: available functions:\n {}\navailable types:\n {}", + inlined_signature, + runtime.functions().join("\n "), + runtime.types().map(|(k, v)| format!("{k}: {v}")).join("\n "), + ) + })?; + Ok(identifier.into()) +} + +/// Generate a function identifier in v0.1 format from the function signature. +fn wasm_identifier_v1( + name: &str, + args: &[DataType], + ret: &DataType, + table_function: bool, +) -> String { + format!( + "{}({}){}{}", + name, + args.iter().map(datatype_name).join(","), + if table_function { "->>" } else { "->" }, + datatype_name(ret) + ) +} + +/// Convert a data type to string used in identifier. +fn datatype_name(ty: &DataType) -> String { + match ty { + DataType::Boolean => "boolean".to_string(), + DataType::Int16 => "int16".to_string(), + DataType::Int32 => "int32".to_string(), + DataType::Int64 => "int64".to_string(), + DataType::Float32 => "float32".to_string(), + DataType::Float64 => "float64".to_string(), + DataType::Date => "date32".to_string(), + DataType::Time => "time64".to_string(), + DataType::Timestamp => "timestamp".to_string(), + DataType::Timestamptz => "timestamptz".to_string(), + DataType::Interval => "interval".to_string(), + DataType::Decimal => "decimal".to_string(), + DataType::Jsonb => "json".to_string(), + DataType::Serial => "serial".to_string(), + DataType::Int256 => "int256".to_string(), + DataType::Bytea => "binary".to_string(), + DataType::Varchar => "string".to_string(), + DataType::List(inner) => format!("{}[]", datatype_name(inner)), + DataType::Struct(s) => format!( + "struct<{}>", + s.iter() + .map(|(name, ty)| format!("{}:{}", name, datatype_name(ty))) + .join(",") + ), + } +} diff --git a/src/expr/core/src/window_function/state/aggregate.rs b/src/expr/impl/src/window_function/aggregate.rs similarity index 88% rename from src/expr/core/src/window_function/state/aggregate.rs rename to src/expr/impl/src/window_function/aggregate.rs index 45d3729702932..9942581357f77 100644 --- a/src/expr/core/src/window_function/state/aggregate.rs +++ b/src/expr/impl/src/window_function/aggregate.rs @@ -20,16 +20,18 @@ use risingwave_common::types::{DataType, Datum}; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common::{bail, must_match}; use risingwave_common_estimate_size::{EstimateSize, KvSize}; +use risingwave_expr::aggregate::{ + AggCall, AggregateFunction, AggregateState as AggImplState, BoxedAggregateFunction, +}; +use risingwave_expr::sig::FUNCTION_REGISTRY; +use risingwave_expr::window_function::{ + BoxedWindowState, FrameBounds, StateEvictHint, StateKey, StatePos, WindowFuncCall, + WindowFuncKind, WindowState, +}; +use risingwave_expr::Result; use smallvec::SmallVec; -use super::buffer::{RangeWindow, RowsWindow, WindowBuffer, WindowImpl}; -use super::{BoxedWindowState, StateEvictHint, StateKey, StatePos, WindowState}; -use crate::aggregate::{ - AggArgs, AggCall, AggregateFunction, AggregateState as AggImplState, BoxedAggregateFunction, -}; -use crate::sig::FUNCTION_REGISTRY; -use crate::window_function::{FrameBounds, WindowFuncCall, WindowFuncKind}; -use crate::Result; +use super::buffer::{RangeWindow, RowsWindow, SessionWindow, WindowBuffer, WindowImpl}; type StateValue = SmallVec<[Datum; 2]>; @@ -52,12 +54,7 @@ pub(super) fn new(call: &WindowFuncCall) -> Result { let arg_data_types = call.args.arg_types().to_vec(); let agg_call = AggCall { kind: agg_kind, - args: match &call.args { - // convert args to [0] or [0, 1] - AggArgs::None => AggArgs::None, - AggArgs::Unary(data_type, _) => AggArgs::Unary(data_type.to_owned(), 0), - AggArgs::Binary(data_types, _) => AggArgs::Binary(data_types.to_owned(), [0, 1]), - }, + args: call.args.clone(), return_type: call.return_type.clone(), column_orders: Vec::new(), // the input is already sorted // TODO(rc): support filter on window function call @@ -65,6 +62,7 @@ pub(super) fn new(call: &WindowFuncCall) -> Result { // TODO(rc): support distinct on window function call? PG doesn't support it either. distinct: false, direct_args: vec![], + user_defined: None, }; let agg_func_sig = FUNCTION_REGISTRY .get(agg_kind, &arg_data_types, &call.return_type) @@ -72,7 +70,7 @@ pub(super) fn new(call: &WindowFuncCall) -> Result { let agg_func = agg_func_sig.build_aggregate(&agg_call)?; let (agg_impl, enable_delta) = if agg_func_sig.is_retractable() && call.frame.exclusion.is_no_others() { - let init_state = agg_func.create_state(); + let init_state = agg_func.create_state()?; (AggImpl::Incremental(init_state), true) } else { (AggImpl::Full, false) @@ -101,6 +99,17 @@ pub(super) fn new(call: &WindowFuncCall) -> Result { ), buffer_heap_size: KvSize::new(), }) as BoxedWindowState, + FrameBounds::Session(frame_bounds) => Box::new(AggregateState { + agg_func, + agg_impl, + arg_data_types, + buffer: WindowBuffer::>::new( + SessionWindow::new(frame_bounds.clone()), + call.frame.exclusion, + enable_delta, + ), + buffer_heap_size: KvSize::new(), + }) as BoxedWindowState, }; Ok(this) } @@ -212,7 +221,7 @@ impl AggregatorWrapper<'_> { where V: AsRef<[Datum]>, { - let mut state = self.agg_func.create_state(); + let mut state = self.agg_func.create_state()?; self.update( &mut state, values.into_iter().map(|args| (Op::Insert, args)), diff --git a/src/expr/core/src/window_function/state/buffer.rs b/src/expr/impl/src/window_function/buffer.rs similarity index 59% rename from src/expr/core/src/window_function/state/buffer.rs rename to src/expr/impl/src/window_function/buffer.rs index ba064042c0021..bd1c10d162b23 100644 --- a/src/expr/core/src/window_function/state/buffer.rs +++ b/src/expr/impl/src/window_function/buffer.rs @@ -18,12 +18,12 @@ use std::ops::Range; use educe::Educe; use risingwave_common::array::Op; use risingwave_common::types::Sentinelled; -use risingwave_common::util::memcmp_encoding; +use risingwave_common::util::memcmp_encoding::{self, MemcmpEncoded}; +use risingwave_expr::window_function::{ + FrameExclusion, RangeFrameBounds, RowsFrameBounds, SessionFrameBounds, StateKey, +}; -use super::range_utils::range_except; -use super::StateKey; -use crate::window_function::state::range_utils::range_diff; -use crate::window_function::{FrameExclusion, RangeFrameBounds, RowsFrameBounds}; +use super::range_utils::{range_diff, range_except}; /// A common sliding window buffer. pub(super) struct WindowBuffer { @@ -89,7 +89,7 @@ impl WindowBuffer { /// Get the current window info. pub fn curr_window(&self) -> CurrWindow<'_, W::Key> { - let buffer_ref = BufferRef { + let window = BufferRef { buffer: &self.buffer, curr_idx: self.curr_idx, left_idx: self.left_idx, @@ -97,8 +97,8 @@ impl WindowBuffer { }; CurrWindow { key: self.curr_key(), - preceding_saturated: self.window_impl.preceding_saturated(buffer_ref), - following_saturated: self.window_impl.following_saturated(buffer_ref), + preceding_saturated: self.window_impl.preceding_saturated(window), + following_saturated: self.window_impl.following_saturated(window), } } @@ -148,7 +148,7 @@ impl WindowBuffer { let old_outer = self.curr_window_outer(); self.buffer.push_back(Entry { key, value }); - self.recalculate_left_right(); + self.recalculate_left_right(RecalculateHint::Append); if self.curr_delta.is_some() { self.maintain_delta(old_outer, self.curr_window_outer()); @@ -161,7 +161,7 @@ impl WindowBuffer { let old_outer = self.curr_window_outer(); self.curr_idx += 1; - self.recalculate_left_right(); + self.recalculate_left_right(RecalculateHint::Slide); if self.curr_delta.is_some() { self.maintain_delta(old_outer, self.curr_window_outer()); @@ -171,6 +171,9 @@ impl WindowBuffer { self.curr_idx -= min_needed_idx; self.left_idx -= min_needed_idx; self.right_excl_idx -= min_needed_idx; + + self.window_impl.shift_indices(min_needed_idx); + self.buffer .drain(0..min_needed_idx) .map(|Entry { key, value }| (key, value)) @@ -189,14 +192,14 @@ impl WindowBuffer { } } - fn recalculate_left_right(&mut self) { - let buffer_ref = BufferRefMut { + fn recalculate_left_right(&mut self, hint: RecalculateHint) { + let window = BufferRefMut { buffer: &self.buffer, curr_idx: &mut self.curr_idx, left_idx: &mut self.left_idx, right_excl_idx: &mut self.right_excl_idx, }; - self.window_impl.recalculate_left_right(buffer_ref); + self.window_impl.recalculate_left_right(window, hint); } } @@ -218,6 +221,12 @@ pub(super) struct BufferRefMut<'a, K: Ord, V: Clone> { right_excl_idx: &'a mut usize, } +#[derive(Clone, Copy, PartialEq, Eq)] +pub(super) enum RecalculateHint { + Append, + Slide, +} + /// A trait for sliding window implementations. This trait is used by [`WindowBuffer`] to /// determine the status of current window and how to slide the window. pub(super) trait WindowImpl { @@ -227,14 +236,22 @@ pub(super) trait WindowImpl { /// Whether the preceding half of the current window is saturated. /// By "saturated" we mean that every row that is possible to be in the preceding half of the /// current window is already in the buffer. - fn preceding_saturated(&self, buffer_ref: BufferRef<'_, Self::Key, Self::Value>) -> bool; + fn preceding_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool; /// Whether the following half of the current window is saturated. - fn following_saturated(&self, buffer_ref: BufferRef<'_, Self::Key, Self::Value>) -> bool; + fn following_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool; /// Recalculate the left and right indices of the current window, according to the latest - /// `curr_idx`. The indices are indices in the buffer vector. - fn recalculate_left_right(&self, buffer_ref: BufferRefMut<'_, Self::Key, Self::Value>); + /// `curr_idx` and the hint. + fn recalculate_left_right( + &mut self, + window: BufferRefMut<'_, Self::Key, Self::Value>, + hint: RecalculateHint, + ); + + /// Shift the indices stored by the [`WindowImpl`] by `n` positions. This should be called + /// after evicting rows from the buffer. + fn shift_indices(&mut self, n: usize); } /// The sliding window implementation for `ROWS` frames. @@ -258,8 +275,8 @@ impl WindowImpl for RowsWindow { type Key = K; type Value = V; - fn preceding_saturated(&self, buffer_ref: BufferRef<'_, Self::Key, Self::Value>) -> bool { - buffer_ref.curr_idx < buffer_ref.buffer.len() && { + fn preceding_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool { + window.curr_idx < window.buffer.len() && { let start_off = self.frame_bounds.start.to_offset(); if let Some(start_off) = start_off { if start_off >= 0 { @@ -269,9 +286,9 @@ impl WindowImpl for RowsWindow { // the following can be simplified. #[allow(clippy::nonminimal_bool)] { - assert!(buffer_ref.curr_idx >= buffer_ref.left_idx); + assert!(window.curr_idx >= window.left_idx); } - buffer_ref.curr_idx - buffer_ref.left_idx >= start_off.unsigned_abs() + window.curr_idx - window.left_idx >= start_off.unsigned_abs() } } else { false // unbounded frame start, never preceding-saturated @@ -279,8 +296,8 @@ impl WindowImpl for RowsWindow { } } - fn following_saturated(&self, buffer_ref: BufferRef<'_, Self::Key, Self::Value>) -> bool { - buffer_ref.curr_idx < buffer_ref.buffer.len() && { + fn following_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool { + window.curr_idx < window.buffer.len() && { let end_off = self.frame_bounds.end.to_offset(); if let Some(end_off) = end_off { if end_off <= 0 { @@ -289,11 +306,11 @@ impl WindowImpl for RowsWindow { // FIXME(rc): Ditto. #[allow(clippy::nonminimal_bool)] { - assert!(buffer_ref.right_excl_idx > 0); - assert!(buffer_ref.right_excl_idx > buffer_ref.curr_idx); - assert!(buffer_ref.right_excl_idx <= buffer_ref.buffer.len()); + assert!(window.right_excl_idx > 0); + assert!(window.right_excl_idx > window.curr_idx); + assert!(window.right_excl_idx <= window.buffer.len()); } - buffer_ref.right_excl_idx - 1 - buffer_ref.curr_idx >= end_off as usize + window.right_excl_idx - 1 - window.curr_idx >= end_off as usize } } else { false // unbounded frame end, never following-saturated @@ -301,39 +318,44 @@ impl WindowImpl for RowsWindow { } } - fn recalculate_left_right(&self, buffer_ref: BufferRefMut<'_, Self::Key, Self::Value>) { - if buffer_ref.buffer.is_empty() { - *buffer_ref.left_idx = 0; - *buffer_ref.right_excl_idx = 0; + fn recalculate_left_right( + &mut self, + window: BufferRefMut<'_, Self::Key, Self::Value>, + _hint: RecalculateHint, + ) { + if window.buffer.is_empty() { + *window.left_idx = 0; + *window.right_excl_idx = 0; } let start_off = self.frame_bounds.start.to_offset(); let end_off = self.frame_bounds.end.to_offset(); if let Some(start_off) = start_off { - let logical_left_idx = *buffer_ref.curr_idx as isize + start_off; + let logical_left_idx = *window.curr_idx as isize + start_off; if logical_left_idx >= 0 { - *buffer_ref.left_idx = - std::cmp::min(logical_left_idx as usize, buffer_ref.buffer.len()); + *window.left_idx = std::cmp::min(logical_left_idx as usize, window.buffer.len()); } else { - *buffer_ref.left_idx = 0; + *window.left_idx = 0; } } else { // unbounded start - *buffer_ref.left_idx = 0; + *window.left_idx = 0; } if let Some(end_off) = end_off { - let logical_right_excl_idx = *buffer_ref.curr_idx as isize + end_off + 1; + let logical_right_excl_idx = *window.curr_idx as isize + end_off + 1; if logical_right_excl_idx >= 0 { - *buffer_ref.right_excl_idx = - std::cmp::min(logical_right_excl_idx as usize, buffer_ref.buffer.len()); + *window.right_excl_idx = + std::cmp::min(logical_right_excl_idx as usize, window.buffer.len()); } else { - *buffer_ref.right_excl_idx = 0; + *window.right_excl_idx = 0; } } else { // unbounded end - *buffer_ref.right_excl_idx = buffer_ref.buffer.len(); + *window.right_excl_idx = window.buffer.len(); } } + + fn shift_indices(&mut self, _n: usize) {} } /// The sliding window implementation for `RANGE` frames. @@ -355,36 +377,40 @@ impl WindowImpl for RangeWindow { type Key = StateKey; type Value = V; - fn preceding_saturated(&self, buffer_ref: BufferRef<'_, Self::Key, Self::Value>) -> bool { - buffer_ref.curr_idx < buffer_ref.buffer.len() && { + fn preceding_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool { + window.curr_idx < window.buffer.len() && { // XXX(rc): It seems that preceding saturation is not important, may remove later. true } } - fn following_saturated(&self, buffer_ref: BufferRef<'_, Self::Key, Self::Value>) -> bool { - buffer_ref.curr_idx < buffer_ref.buffer.len() + fn following_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool { + window.curr_idx < window.buffer.len() && { // Left OK? (note that `left_idx` can be greater than `right_idx`) // The following line checks whether the left value is the last one in the buffer. // Here we adopt a conservative approach, which means we assume the next future value // is likely to be the same as the last value in the current window, in which case // we can't say the current window is saturated. - buffer_ref.left_idx < buffer_ref.buffer.len() /* non-zero */ - 1 + window.left_idx < window.buffer.len() /* non-zero */ - 1 } && { // Right OK? Ditto. - buffer_ref.right_excl_idx < buffer_ref.buffer.len() + window.right_excl_idx < window.buffer.len() } } - fn recalculate_left_right(&self, buffer_ref: BufferRefMut<'_, Self::Key, Self::Value>) { - if buffer_ref.buffer.is_empty() { - *buffer_ref.left_idx = 0; - *buffer_ref.right_excl_idx = 0; + fn recalculate_left_right( + &mut self, + window: BufferRefMut<'_, Self::Key, Self::Value>, + _hint: RecalculateHint, + ) { + if window.buffer.is_empty() { + *window.left_idx = 0; + *window.right_excl_idx = 0; } - let Some(entry) = buffer_ref.buffer.get(*buffer_ref.curr_idx) else { + let Some(entry) = window.buffer.get(*window.curr_idx) else { // If the current index has been moved to a future position, we can't touch anything // because the next coming key may equal to the previous one which means the left and // right indices will be the same. @@ -403,7 +429,7 @@ impl WindowImpl for RangeWindow { Sentinelled::Smallest => { // unbounded frame start assert_eq!( - *buffer_ref.left_idx, 0, + *window.left_idx, 0, "for unbounded start, left index should always be 0" ); } @@ -411,7 +437,7 @@ impl WindowImpl for RangeWindow { // bounded, find the start position let value_enc = memcmp_encoding::encode_value(value, self.frame_bounds.order_type) .expect("no reason to fail here"); - *buffer_ref.left_idx = buffer_ref + *window.left_idx = window .buffer .partition_point(|elem| elem.key.order_key < value_enc); } @@ -421,29 +447,214 @@ impl WindowImpl for RangeWindow { match self.frame_bounds.frame_end_of(curr_order_value) { Sentinelled::Largest => { // unbounded frame end - *buffer_ref.right_excl_idx = buffer_ref.buffer.len(); + *window.right_excl_idx = window.buffer.len(); } Sentinelled::Normal(value) => { // bounded, find the end position let value_enc = memcmp_encoding::encode_value(value, self.frame_bounds.order_type) .expect("no reason to fail here"); - *buffer_ref.right_excl_idx = buffer_ref + *window.right_excl_idx = window .buffer .partition_point(|elem| elem.key.order_key <= value_enc); } Sentinelled::Smallest => unreachable!("frame end never be UNBOUNDED PRECEDING"), } } + + fn shift_indices(&mut self, _n: usize) {} +} + +pub(super) struct SessionWindow { + frame_bounds: SessionFrameBounds, + /// The latest session is the rightmost session in the buffer, which is updated during appending. + latest_session: Option, + /// The sizes of recognized but not consumed sessions in the buffer. It's updated during appending. + /// The first element, if any, should be the size of the "current session window". When sliding, + /// the front should be popped. + recognized_session_sizes: VecDeque, + _phantom: std::marker::PhantomData, +} + +#[derive(Debug)] +struct LatestSession { + /// The starting index of the latest session. + start_idx: usize, + + /// Minimal next start means the minimal order value that can start a new session. + /// If a row has an order value less than this, it should be in the current session. + minimal_next_start: MemcmpEncoded, +} + +impl SessionWindow { + pub fn new(frame_bounds: SessionFrameBounds) -> Self { + Self { + frame_bounds, + latest_session: None, + recognized_session_sizes: Default::default(), + _phantom: std::marker::PhantomData, + } + } +} + +impl WindowImpl for SessionWindow { + type Key = StateKey; + type Value = V; + + fn preceding_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool { + window.curr_idx < window.buffer.len() && { + // XXX(rc): It seems that preceding saturation is not important, may remove later. + true + } + } + + fn following_saturated(&self, window: BufferRef<'_, Self::Key, Self::Value>) -> bool { + window.curr_idx < window.buffer.len() && { + // For session window, `left_idx` is always smaller than `right_excl_idx`. + assert!(window.left_idx <= window.curr_idx); + assert!(window.curr_idx < window.right_excl_idx); + + // The following expression checks whether the current window is the latest session. + // If it is, we don't think it's saturated because the next row may be still in the + // same session. Otherwise, we can safely say it's saturated. + self.latest_session + .as_ref() + .map_or(false, |LatestSession { start_idx, .. }| { + window.left_idx < *start_idx + }) + } + } + + fn recalculate_left_right( + &mut self, + window: BufferRefMut<'_, Self::Key, Self::Value>, + hint: RecalculateHint, + ) { + // Terms: + // - Session: A continuous range of rows among any two of which the difference of order values + // is less than the session gap. This is a concept on the whole stream. Sessions are recognized + // during appending. + // - Current window: The range of rows that are represented by the indices in `window`. It is a + // status of the `WindowBuffer`. If the current window happens to be the last session in the + // buffer, it will be updated during appending. Otherwise it will only be updated during sliding. + + match hint { + RecalculateHint::Append => { + assert!(!window.buffer.is_empty()); // because we just appended a row + let appended_idx = window.buffer.len() - 1; + let appended_key = &window.buffer[appended_idx].key; + + let minimal_next_start_of_appended = self.frame_bounds.minimal_next_start_of( + memcmp_encoding::decode_value( + &self.frame_bounds.order_data_type, + &appended_key.order_key, + self.frame_bounds.order_type, + ) + .expect("no reason to fail here because we just encoded it in memory"), + ); + let minimal_next_start_enc_of_appended = memcmp_encoding::encode_value( + minimal_next_start_of_appended, + self.frame_bounds.order_type, + ) + .expect("no reason to fail here"); + + if let Some(LatestSession { + ref start_idx, + minimal_next_start, + }) = self.latest_session.as_mut() + { + if &appended_key.order_key >= minimal_next_start { + // the appended row starts a new session + self.recognized_session_sizes + .push_back(appended_idx - start_idx); + self.latest_session = Some(LatestSession { + start_idx: appended_idx, + minimal_next_start: minimal_next_start_enc_of_appended, + }); + // no need to update the current window because it's now corresponding + // to some previous session + } else { + // the appended row belongs to the latest session + *minimal_next_start = minimal_next_start_enc_of_appended; + + if *start_idx == *window.left_idx { + // the current window is the latest session, we should extend it + *window.right_excl_idx = appended_idx + 1; + } + } + } else { + // no session yet, the current window should be empty + let left_idx = *window.left_idx; + let curr_idx = *window.curr_idx; + let old_right_excl_idx = *window.right_excl_idx; + assert_eq!(left_idx, curr_idx); + assert_eq!(left_idx, old_right_excl_idx); + assert_eq!(old_right_excl_idx, window.buffer.len() - 1); + + // now we put the first row into the current window + *window.right_excl_idx = window.buffer.len(); + + // and start to recognize the latest session + self.latest_session = Some(LatestSession { + start_idx: left_idx, + minimal_next_start: minimal_next_start_enc_of_appended, + }); + } + } + RecalculateHint::Slide => { + let old_left_idx = *window.left_idx; + let new_curr_idx = *window.curr_idx; + let old_right_excl_idx = *window.right_excl_idx; + + if new_curr_idx < old_right_excl_idx { + // the current row is still in the current session window, no need to slide + } else { + let old_session_size = self.recognized_session_sizes.pop_front(); + let next_session_size = self.recognized_session_sizes.front().copied(); + + if let Some(old_session_size) = old_session_size { + assert_eq!(old_session_size, old_right_excl_idx - old_left_idx); + + // slide the window to the next session + if let Some(next_session_size) = next_session_size { + // the next session is fully recognized, so we know the ending index + *window.left_idx = old_right_excl_idx; + *window.right_excl_idx = old_right_excl_idx + next_session_size; + } else { + // the next session is still in recognition, so we end the window at the end of buffer + *window.left_idx = old_right_excl_idx; + *window.right_excl_idx = window.buffer.len(); + } + } else { + // no recognized session yet, meaning the current window is the last session in the buffer + assert_eq!(old_right_excl_idx, window.buffer.len()); + *window.left_idx = old_right_excl_idx; + *window.right_excl_idx = old_right_excl_idx; + self.latest_session = None; + } + } + } + } + } + + fn shift_indices(&mut self, n: usize) { + if let Some(LatestSession { start_idx, .. }) = self.latest_session.as_mut() { + *start_idx -= n; + } + } } #[cfg(test)] mod tests { use itertools::Itertools; - - use super::*; - use crate::window_function::FrameBound::{ + use risingwave_common::row::OwnedRow; + use risingwave_common::types::{DataType, ScalarImpl}; + use risingwave_common::util::sort_util::OrderType; + use risingwave_expr::window_function::FrameBound::{ CurrentRow, Following, Preceding, UnboundedFollowing, UnboundedPreceding, }; + use risingwave_expr::window_function::SessionFrameGap; + + use super::*; #[test] fn test_rows_frame_unbounded_preceding_to_current_row() { @@ -735,4 +946,114 @@ mod tests { vec!["hello"] ); } + + #[test] + fn test_session_frame() { + let order_data_type = DataType::Int64; + let order_type = OrderType::ascending(); + let gap_data_type = DataType::Int64; + + let mut buffer = WindowBuffer::>::new( + SessionWindow::new(SessionFrameBounds { + order_data_type: order_data_type.clone(), + order_type, + gap_data_type: gap_data_type.clone(), + gap: SessionFrameGap::new_for_test( + ScalarImpl::Int64(5), + &order_data_type, + &gap_data_type, + ), + }), + FrameExclusion::NoOthers, + true, + ); + + let key = |key: i64| -> StateKey { + StateKey { + order_key: memcmp_encoding::encode_value(&Some(ScalarImpl::from(key)), order_type) + .unwrap(), + pk: OwnedRow::empty().into(), + } + }; + + assert!(buffer.curr_key().is_none()); + + buffer.append(key(1), "hello"); + buffer.append(key(3), "session"); + let window = buffer.curr_window(); + assert_eq!(window.key, Some(&key(1))); + assert!(window.preceding_saturated); + assert!(!window.following_saturated); + assert_eq!( + buffer.curr_window_values().cloned().collect_vec(), + vec!["hello", "session"] + ); + + buffer.append(key(8), "window"); // start a new session + let window = buffer.curr_window(); + assert!(window.following_saturated); + assert_eq!( + buffer.curr_window_values().cloned().collect_vec(), + vec!["hello", "session"] + ); + + buffer.append(key(15), "and"); + buffer.append(key(16), "world"); + assert_eq!( + buffer.curr_window_values().cloned().collect_vec(), + vec!["hello", "session"] + ); + + let removed_keys = buffer.slide().map(|(k, _)| k).collect_vec(); + assert!(removed_keys.is_empty()); + let window = buffer.curr_window(); + assert_eq!(window.key, Some(&key(3))); + assert!(window.preceding_saturated); + assert!(window.following_saturated); + assert_eq!( + buffer.curr_window_values().cloned().collect_vec(), + vec!["hello", "session"] + ); + + let removed_keys = buffer.slide().map(|(k, _)| k).collect_vec(); + assert_eq!(removed_keys, vec![key(1), key(3)]); + assert_eq!(buffer.smallest_key(), Some(&key(8))); + let window = buffer.curr_window(); + assert_eq!(window.key, Some(&key(8))); + assert!(window.preceding_saturated); + assert!(window.following_saturated); + assert_eq!( + buffer.curr_window_values().cloned().collect_vec(), + vec!["window"] + ); + + let removed_keys = buffer.slide().map(|(k, _)| k).collect_vec(); + assert_eq!(removed_keys, vec![key(8)]); + assert_eq!(buffer.smallest_key(), Some(&key(15))); + let window = buffer.curr_window(); + assert_eq!(window.key, Some(&key(15))); + assert!(window.preceding_saturated); + assert!(!window.following_saturated); + assert_eq!( + buffer.curr_window_values().cloned().collect_vec(), + vec!["and", "world"] + ); + + let removed_keys = buffer.slide().map(|(k, _)| k).collect_vec(); + assert!(removed_keys.is_empty()); + assert_eq!(buffer.curr_key(), Some(&key(16))); + assert_eq!( + buffer.curr_window_values().cloned().collect_vec(), + vec!["and", "world"] + ); + + let removed_keys = buffer.slide().map(|(k, _)| k).collect_vec(); + assert_eq!(removed_keys, vec![key(15), key(16)]); + assert!(buffer.curr_key().is_none()); + assert!(buffer + .curr_window_values() + .cloned() + .collect_vec() + .is_empty()); + } } diff --git a/src/expr/impl/src/window_function/mod.rs b/src/expr/impl/src/window_function/mod.rs new file mode 100644 index 0000000000000..1153a9111a1ea --- /dev/null +++ b/src/expr/impl/src/window_function/mod.rs @@ -0,0 +1,45 @@ +// Copyright 2024 RisingWave Labs +// +// 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 itertools::Itertools; +use risingwave_expr::window_function::{ + BoxedWindowState, WindowFuncCall, WindowFuncKind, WINDOW_STATE_BUILDERS, +}; +use risingwave_expr::{ExprError, Result}; + +mod aggregate; +mod buffer; +mod range_utils; +mod rank; + +#[linkme::distributed_slice(WINDOW_STATE_BUILDERS)] +fn create_window_state_impl(call: &WindowFuncCall) -> Result { + assert!(call.frame.bounds.validate().is_ok()); + + use WindowFuncKind::*; + Ok(match call.kind { + RowNumber => Box::new(rank::RankState::::new(call)), + Rank => Box::new(rank::RankState::::new(call)), + DenseRank => Box::new(rank::RankState::::new(call)), + Aggregate(_) => aggregate::new(call)?, + kind => { + return Err(ExprError::UnsupportedFunction(format!( + "{}({}) -> {}", + kind, + call.args.arg_types().iter().format(", "), + &call.return_type, + ))); + } + }) +} diff --git a/src/expr/core/src/window_function/state/range_utils.rs b/src/expr/impl/src/window_function/range_utils.rs similarity index 100% rename from src/expr/core/src/window_function/state/range_utils.rs rename to src/expr/impl/src/window_function/range_utils.rs diff --git a/src/expr/core/src/window_function/state/rank.rs b/src/expr/impl/src/window_function/rank.rs similarity index 95% rename from src/expr/core/src/window_function/state/rank.rs rename to src/expr/impl/src/window_function/rank.rs index 238af18699315..9b25a15c35590 100644 --- a/src/expr/core/src/window_function/state/rank.rs +++ b/src/expr/impl/src/window_function/rank.rs @@ -18,12 +18,13 @@ use risingwave_common::types::Datum; use risingwave_common::util::memcmp_encoding::MemcmpEncoded; use risingwave_common_estimate_size::collections::EstimatedVecDeque; use risingwave_common_estimate_size::EstimateSize; +use risingwave_expr::window_function::{ + StateEvictHint, StateKey, StatePos, WindowFuncCall, WindowState, +}; +use risingwave_expr::Result; use smallvec::SmallVec; use self::private::RankFuncCount; -use super::{StateEvictHint, StateKey, StatePos, WindowState}; -use crate::window_function::WindowFuncCall; -use crate::Result; mod private { use super::*; @@ -34,7 +35,7 @@ mod private { } #[derive(Default, EstimateSize)] -pub struct RowNumber { +pub(super) struct RowNumber { prev_rank: i64, } @@ -47,7 +48,7 @@ impl RankFuncCount for RowNumber { } #[derive(EstimateSize)] -pub struct Rank { +pub(super) struct Rank { prev_order_key: Option, prev_rank: i64, prev_pos_in_peer_group: i64, @@ -83,7 +84,7 @@ impl RankFuncCount for Rank { } #[derive(Default, EstimateSize)] -pub struct DenseRank { +pub(super) struct DenseRank { prev_order_key: Option, prev_rank: i64, } @@ -107,7 +108,7 @@ impl RankFuncCount for DenseRank { /// Generic state for rank window functions including `row_number`, `rank` and `dense_rank`. #[derive(EstimateSize)] -pub struct RankState { +pub(super) struct RankState { /// First state key of the partition. first_key: Option, /// State keys that are waiting to be outputted. @@ -176,10 +177,10 @@ mod tests { use risingwave_common::types::{DataType, ScalarImpl}; use risingwave_common::util::memcmp_encoding; use risingwave_common::util::sort_util::OrderType; + use risingwave_expr::aggregate::AggArgs; + use risingwave_expr::window_function::{Frame, FrameBound, WindowFuncKind}; use super::*; - use crate::aggregate::AggArgs; - use crate::window_function::{Frame, FrameBound, WindowFuncKind}; fn create_state_key(order: i64, pk: i64) -> StateKey { StateKey { @@ -197,7 +198,7 @@ mod tests { fn test_rank_state_bad_use() { let call = WindowFuncCall { kind: WindowFuncKind::RowNumber, - args: AggArgs::None, + args: AggArgs::default(), return_type: DataType::Int64, frame: Frame::rows( FrameBound::UnboundedPreceding, @@ -214,7 +215,7 @@ mod tests { fn test_row_number_state() { let call = WindowFuncCall { kind: WindowFuncKind::RowNumber, - args: AggArgs::None, + args: AggArgs::default(), return_type: DataType::Int64, frame: Frame::rows( FrameBound::UnboundedPreceding, @@ -256,7 +257,7 @@ mod tests { fn test_rank_state() { let call = WindowFuncCall { kind: WindowFuncKind::Rank, - args: AggArgs::None, + args: AggArgs::default(), return_type: DataType::Int64, frame: Frame::rows( FrameBound::UnboundedPreceding, @@ -297,7 +298,7 @@ mod tests { fn test_dense_rank_state() { let call = WindowFuncCall { kind: WindowFuncKind::DenseRank, - args: AggArgs::None, + args: AggArgs::default(), return_type: DataType::Int64, frame: Frame::rows( FrameBound::UnboundedPreceding, diff --git a/src/expr/macro/src/gen.rs b/src/expr/macro/src/gen.rs index ca3203033a63e..3494f52406193 100644 --- a/src/expr/macro/src/gen.rs +++ b/src/expr/macro/src/gen.rs @@ -342,10 +342,45 @@ impl FunctionAttr { // no prebuilt argument (None, _) => quote! {}, }; - let variadic_args = variadic.then(|| quote! { variadic_row, }); + let variadic_args = variadic.then(|| quote! { &variadic_row, }); let context = user_fn.context.then(|| quote! { &self.context, }); let writer = user_fn.write.then(|| quote! { &mut writer, }); let await_ = user_fn.async_.then(|| quote! { .await }); + + let record_error = { + // Uniform arguments into `DatumRef`. + #[allow(clippy::disallowed_methods)] // allow zip + let inputs_args = inputs + .iter() + .zip(user_fn.args_option.iter()) + .map(|(input, opt)| { + if *opt { + quote! { #input.map(|s| ScalarRefImpl::from(s)) } + } else { + quote! { Some(ScalarRefImpl::from(#input)) } + } + }); + let inputs_args = quote! { + let args: &[DatumRef<'_>] = &[#(#inputs_args),*]; + let args = args.iter().copied(); + }; + let var_args = variadic.then(|| { + quote! { + let args = args.chain(variadic_row.iter()); + } + }); + + quote! { + #inputs_args + #var_args + errors.push(ExprError::function( + stringify!(#fn_name), + args, + e, + )); + } + }; + // call the user defined function // inputs: [ Option ] let mut output = quote! { #fn_name #generic( @@ -365,13 +400,19 @@ impl FunctionAttr { ReturnTypeKind::Result => quote! { match #output { Ok(x) => Some(x), - Err(e) => { errors.push(e); None } + Err(e) => { + #record_error + None + } } }, ReturnTypeKind::ResultOption => quote! { match #output { Ok(x) => x, - Err(e) => { errors.push(e); None } + Err(e) => { + #record_error + None + } } }, }; @@ -484,10 +525,6 @@ impl FunctionAttr { } } else { // no optimization - let array_zip = match children_indices.len() { - 0 => quote! { std::iter::repeat(()).take(input.capacity()) }, - _ => quote! { multizip((#(#arrays.iter(),)*)) }, - }; let let_variadic = variadic.then(|| { quote! { let variadic_row = variadic_input.row_at_unchecked_vis(i); @@ -497,18 +534,18 @@ impl FunctionAttr { let mut builder = #builder_type::with_type(input.capacity(), self.context.return_type.clone()); if input.is_compacted() { - for (i, (#(#inputs,)*)) in #array_zip.enumerate() { + for i in 0..input.capacity() { + #(let #inputs = unsafe { #arrays.value_at_unchecked(i) };)* #let_variadic #append_output } } else { - // allow using `zip` for performance - #[allow(clippy::disallowed_methods)] - for (i, ((#(#inputs,)*), visible)) in #array_zip.zip(input.visibility().iter()).enumerate() { - if !visible { + for i in 0..input.capacity() { + if unsafe { !input.visibility().is_set_unchecked(i) } { builder.append_null(); continue; } + #(let #inputs = unsafe { #arrays.value_at_unchecked(i) };)* #let_variadic #append_output } @@ -524,7 +561,7 @@ impl FunctionAttr { use std::sync::Arc; use risingwave_common::array::*; use risingwave_common::types::*; - use risingwave_common::buffer::Bitmap; + use risingwave_common::bitmap::Bitmap; use risingwave_common::row::OwnedRow; use risingwave_common::util::iter_util::ZipEqFast; @@ -725,15 +762,15 @@ impl FunctionAttr { }; let create_state = if custom_state.is_some() { quote! { - fn create_state(&self) -> AggregateState { - AggregateState::Any(Box::<#state_type>::default()) + fn create_state(&self) -> Result { + Ok(AggregateState::Any(Box::<#state_type>::default())) } } } else if let Some(state) = &self.init_state { let state: TokenStream2 = state.parse().unwrap(); quote! { - fn create_state(&self) -> AggregateState { - AggregateState::Datum(Some(#state.into())) + fn create_state(&self) -> Result { + Ok(AggregateState::Datum(Some(#state.into()))) } } } else { @@ -877,7 +914,7 @@ impl FunctionAttr { use risingwave_common::array::*; use risingwave_common::types::*; use risingwave_common::bail; - use risingwave_common::buffer::Bitmap; + use risingwave_common::bitmap::Bitmap; use risingwave_common_estimate_size::EstimateSize; use risingwave_expr::expr::Context; @@ -1085,11 +1122,29 @@ impl FunctionAttr { let iter = quote! { #fn_name(#(#inputs,)* #prebuilt_arg #context) }; let mut iter = match user_fn.return_type_kind { ReturnTypeKind::T => quote! { #iter }, - ReturnTypeKind::Result => quote! { #iter? }, - ReturnTypeKind::Option => quote! { if let Some(it) = #iter { it } else { continue; } }, - ReturnTypeKind::ResultOption => { - quote! { if let Some(it) = #iter? { it } else { continue; } } - } + ReturnTypeKind::Option => quote! { match #iter { + Some(it) => it, + None => continue, + } }, + ReturnTypeKind::Result => quote! { match #iter { + Ok(it) => it, + Err(e) => { + index_builder.append(Some(i as i32)); + #(#builders.append_null();)* + error_builder.append_display(Some(e.as_report())); + continue; + } + } }, + ReturnTypeKind::ResultOption => quote! { match #iter { + Ok(Some(it)) => it, + Ok(None) => continue, + Err(e) => { + index_builder.append(Some(i as i32)); + #(#builders.append_null();)* + error_builder.append_display(Some(e.as_report())); + continue; + } + } }, }; // if user function accepts non-option arguments, we assume the function // returns empty on null input, so we need to unwrap the inputs before calling. @@ -1116,18 +1171,31 @@ impl FunctionAttr { "expect `impl Iterator` in return type", ) })?; - let output = match iterator_item_type { - ReturnTypeKind::T => quote! { Some(output) }, - ReturnTypeKind::Option => quote! { output }, - ReturnTypeKind::Result => quote! { Some(output?) }, - ReturnTypeKind::ResultOption => quote! { output? }, + let append_output = match iterator_item_type { + ReturnTypeKind::T => quote! { + let (#(#outputs),*) = output; + #(#builders.append(#optioned_outputs);)* error_builder.append_null(); + }, + ReturnTypeKind::Option => quote! { match output { + Some((#(#outputs),*)) => { #(#builders.append(#optioned_outputs);)* error_builder.append_null(); } + None => { #(#builders.append_null();)* error_builder.append_null(); } + } }, + ReturnTypeKind::Result => quote! { match output { + Ok((#(#outputs),*)) => { #(#builders.append(#optioned_outputs);)* error_builder.append_null(); } + Err(e) => { #(#builders.append_null();)* error_builder.append_display(Some(e.as_report())); } + } }, + ReturnTypeKind::ResultOption => quote! { match output { + Ok(Some((#(#outputs),*))) => { #(#builders.append(#optioned_outputs);)* error_builder.append_null(); } + Ok(None) => { #(#builders.append_null();)* error_builder.append_null(); } + Err(e) => { #(#builders.append_null();)* error_builder.append_display(Some(e.as_report())); } + } }, }; Ok(quote! { |return_type, chunk_size, children| { use risingwave_common::array::*; use risingwave_common::types::*; - use risingwave_common::buffer::Bitmap; + use risingwave_common::bitmap::Bitmap; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_expr::expr::{BoxedExpression, Context}; use risingwave_expr::{Result, ExprError}; @@ -1178,24 +1246,28 @@ impl FunctionAttr { let mut index_builder = I32ArrayBuilder::new(self.chunk_size); #(let mut #builders = #builder_types::with_type(self.chunk_size, #return_types);)* + let mut error_builder = Utf8ArrayBuilder::new(self.chunk_size); - for (i, ((#(#inputs,)*), visible)) in multizip((#(#arrays.iter(),)*)).zip_eq_fast(input.visibility().iter()).enumerate() { - if !visible { + for i in 0..input.capacity() { + if unsafe { !input.visibility().is_set_unchecked(i) } { continue; } + #(let #inputs = unsafe { #arrays.value_at_unchecked(i) };)* for output in #iter { index_builder.append(Some(i as i32)); - match #output { - Some((#(#outputs),*)) => { #(#builders.append(#optioned_outputs);)* } - None => { #(#builders.append_null();)* } - } + #append_output if index_builder.len() == self.chunk_size { let len = index_builder.len(); let index_array = std::mem::replace(&mut index_builder, I32ArrayBuilder::new(self.chunk_size)).finish().into_ref(); let value_arrays = [#(std::mem::replace(&mut #builders, #builder_types::with_type(self.chunk_size, #return_types)).finish().into_ref()),*]; #build_value_array - yield DataChunk::new(vec![index_array, value_array], self.chunk_size); + let error_array = std::mem::replace(&mut error_builder, Utf8ArrayBuilder::new(self.chunk_size)).finish().into_ref(); + if error_array.null_bitmap().any() { + yield DataChunk::new(vec![index_array, value_array, error_array], self.chunk_size); + } else { + yield DataChunk::new(vec![index_array, value_array], self.chunk_size); + } } } } @@ -1205,7 +1277,12 @@ impl FunctionAttr { let index_array = index_builder.finish().into_ref(); let value_arrays = [#(#builders.finish().into_ref()),*]; #build_value_array - yield DataChunk::new(vec![index_array, value_array], len); + let error_array = error_builder.finish().into_ref(); + if error_array.null_bitmap().any() { + yield DataChunk::new(vec![index_array, value_array, error_array], len); + } else { + yield DataChunk::new(vec![index_array, value_array], len); + } } } } diff --git a/src/expr/udf/Cargo.toml b/src/expr/udf/Cargo.toml deleted file mode 100644 index b17ad7acadfc1..0000000000000 --- a/src/expr/udf/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "risingwave_udf" -version = "0.1.0" -edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[package.metadata.cargo-machete] -ignored = ["workspace-hack"] - -[package.metadata.cargo-udeps.ignore] -normal = ["workspace-hack"] - -[dependencies] -arrow-array = { workspace = true } -arrow-flight = { workspace = true } -arrow-schema = { workspace = true } -arrow-select = { workspace = true } -cfg-or-panic = "0.2" -futures = "0.3" -futures-util = "0.3.28" -ginepro = "0.7.0" -prometheus = "0.13" -risingwave_common = { workspace = true } -static_assertions = "1" -thiserror = "1" -thiserror-ext = { workspace = true } -tokio = { version = "0.2", package = "madsim-tokio", features = [ - "rt", - "macros", -] } -tonic = { workspace = true } -tracing = "0.1" - -[lints] -workspace = true diff --git a/src/expr/udf/README-js.md b/src/expr/udf/README-js.md deleted file mode 100644 index 902bce4ef52ee..0000000000000 --- a/src/expr/udf/README-js.md +++ /dev/null @@ -1,83 +0,0 @@ -# Use UDFs in JavaScript - -This article provides a step-by-step guide for defining JavaScript functions in RisingWave. - -JavaScript code is inlined in `CREATE FUNCTION` statement and then run on the embedded QuickJS virtual machine in RisingWave. It does not support access to external networks and is limited to computational tasks only. -Compared to other languages, JavaScript UDFs offer the easiest way to define UDFs in RisingWave. - -## Define your functions - -You can use the `CREATE FUNCTION` statement to create JavaScript UDFs. The syntax is as follows: - -```sql -CREATE FUNCTION function_name ( arg_name arg_type [, ...] ) - [ RETURNS return_type | RETURNS TABLE ( column_name column_type [, ...] ) ] - LANGUAGE javascript - AS [ $$ function_body $$ | 'function_body' ]; -``` - -The argument names you define can be used in the function body. For example: - -```sql -CREATE FUNCTION gcd(a int, b int) RETURNS int LANGUAGE javascript AS $$ - if(a == null || b == null) { - return null; - } - while (b != 0) { - let t = b; - b = a % b; - a = t; - } - return a; -$$; -``` - -The correspondence between SQL types and JavaScript types can be found in the [appendix table](#appendix-type-mapping). You need to ensure that the type of the return value is either `null` or consistent with the type in the `RETURNS` clause. - -If the function you define returns a table, you need to use the `yield` statement to return the data of each row. For example: - -```sql -CREATE FUNCTION series(n int) RETURNS TABLE (x int) LANGUAGE javascript AS $$ - for(let i = 0; i < n; i++) { - yield i; - } -$$; -``` - -## Use your functions - -Once the UDFs are created in RisingWave, you can use them in SQL queries just like any built-in functions. For example: - -```sql -SELECT gcd(25, 15); -SELECT * from series(5); -``` - -## Appendix: Type Mapping - -The following table shows the type mapping between SQL and JavaScript: - -| SQL Type | JS Type | Note | -| --------------------- | ------------- | --------------------- | -| boolean | boolean | | -| smallint | number | | -| int | number | | -| bigint | number | | -| real | number | | -| double precision | number | | -| decimal | BigDecimal | | -| date | | not supported yet | -| time | | not supported yet | -| timestamp | | not supported yet | -| timestamptz | | not supported yet | -| interval | | not supported yet | -| varchar | string | | -| bytea | Uint8Array | | -| jsonb | null, boolean, number, string, array or object | `JSON.parse(string)` | -| smallint[] | Int16Array | | -| int[] | Int32Array | | -| bigint[] | BigInt64Array | | -| real[] | Float32Array | | -| double precision[] | Float64Array | | -| others[] | array | | -| struct<..> | object | | diff --git a/src/expr/udf/README.md b/src/expr/udf/README.md deleted file mode 100644 index d9428cc547249..0000000000000 --- a/src/expr/udf/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# Use UDFs in Rust - -This article provides a step-by-step guide for defining Rust functions in RisingWave. - -Rust functions are compiled into WebAssembly modules and then run on the embedded WebAssembly virtual machine in RisingWave. Compared to Python and Java, Rust UDFs offer **higher performance** (near native) and are **managed by the RisingWave kernel**, eliminating the need for additional maintenance. However, since they run embedded in the kernel, for security reasons, Rust UDFs currently **do not support access to external networks and are limited to computational tasks only**, with restricted CPU and memory resources. Therefore, we recommend using Rust UDFs for **computationally intensive tasks**, such as packet parsing and format conversion. - -## Prerequisites - -- Ensure that you have [Rust toolchain](https://rustup.rs) (stable channel) installed on your computer. -- Ensure that the Rust standard library for `wasm32-wasi` target is installed: - ```shell - rustup target add wasm32-wasi - ``` - -## 1. Create a project - -Create a Rust project named `udf`: - -```shell -cargo new --lib udf -cd udf -``` - -Add the following lines to `Cargo.toml`: - -```toml -[lib] -crate-type = ["cdylib"] - -[dependencies] -arrow-udf = "0.1" -``` - -## 2. Define your functions - -In `src/lib.rs`, define your functions using the `function` macro: - -```rust -use arrow_udf::function; - -// define a scalar function -#[function("gcd(int, int) -> int")] -fn gcd(mut x: i32, mut y: i32) -> i32 { - while y != 0 { - (x, y) = (y, x % y); - } - x -} - -// define a table function -#[function("series(int) -> setof int")] -fn series(n: i32) -> impl Iterator { - 0..n -} -``` - -You can find more usages in the [documentation](https://docs.rs/arrow_udf/0.1.0/arrow_udf/attr.function.html) and more examples in the [tests](https://github.com/risingwavelabs/arrow-udf/blob/main/arrow-udf/tests/tests.rs). - -Currently we only support a limited set of data types. `timestamptz` and complex array types are not supported yet. - -## 3. Build the project - -Build your functions into a WebAssembly module: - -```shell -cargo build --release --target wasm32-wasi -``` - -You can find the generated WASM module at `target/wasm32-wasi/release/udf.wasm`. - -Optional: It is recommended to strip the binary to reduce its size: - -```shell -# Install wasm-tools -cargo install wasm-tools - -# Strip the binary -wasm-tools strip ./target/wasm32-wasi/release/udf.wasm > udf.wasm -``` - -## 4. Declare your functions in RisingWave - -In RisingWave, use the `CREATE FUNCTION` command to declare the functions you defined. - -There are two ways to load the WASM module: - -1. The WASM binary can be embedded in the SQL statement using the base64 encoding. -You can use the following shell script to encode the binary and generate the SQL statement: - ```shell - encoded=$(base64 -i udf.wasm) - sql="CREATE FUNCTION gcd(int, int) RETURNS int LANGUAGE wasm USING BASE64 '$encoded';" - echo "$sql" > create_function.sql - ``` - When created successfully, the WASM binary will be automatically uploaded to the object store. - -2. The WASM binary can be loaded from the object store. - ```sql - CREATE FUNCTION gcd(int, int) RETURNS int - LANGUAGE wasm USING LINK 's3://bucket/path/to/udf.wasm'; - - CREATE FUNCTION series(int) RETURNS TABLE (x int) - LANGUAGE wasm USING LINK 's3://bucket/path/to/udf.wasm'; - ``` - - Or if you run RisingWave locally, you can use the local file system: - ```sql - CREATE FUNCTION gcd(int, int) RETURNS int - LANGUAGE wasm USING LINK 'fs://path/to/udf.wasm'; - ``` - -## 5. Use your functions in RisingWave - -Once the UDFs are created in RisingWave, you can use them in SQL queries just like any built-in functions. For example: - -```sql -SELECT gcd(25, 15); -SELECT series(5); -``` diff --git a/src/expr/udf/examples/client.rs b/src/expr/udf/examples/client.rs deleted file mode 100644 index 92f93ae13614e..0000000000000 --- a/src/expr/udf/examples/client.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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 arrow_array::{Int32Array, RecordBatch}; -use arrow_schema::{DataType, Field, Schema}; -use risingwave_udf::ArrowFlightUdfClient; - -#[tokio::main] -async fn main() { - let addr = "http://localhost:8815"; - let client = ArrowFlightUdfClient::connect(addr).await.unwrap(); - - // build `RecordBatch` to send (equivalent to our `DataChunk`) - let array1 = Arc::new(Int32Array::from_iter(vec![1, 6, 10])); - let array2 = Arc::new(Int32Array::from_iter(vec![3, 4, 15])); - let array3 = Arc::new(Int32Array::from_iter(vec![6, 8, 3])); - let input2_schema = Schema::new(vec![ - Field::new("a", DataType::Int32, true), - Field::new("b", DataType::Int32, true), - ]); - let input3_schema = Schema::new(vec![ - Field::new("a", DataType::Int32, true), - Field::new("b", DataType::Int32, true), - Field::new("c", DataType::Int32, true), - ]); - let output_schema = Schema::new(vec![Field::new("x", DataType::Int32, true)]); - - // check function - client - .check("gcd", &input2_schema, &output_schema) - .await - .unwrap(); - client - .check("gcd3", &input3_schema, &output_schema) - .await - .unwrap(); - - let input2 = RecordBatch::try_new( - Arc::new(input2_schema), - vec![array1.clone(), array2.clone()], - ) - .unwrap(); - - let output = client - .call("gcd", input2) - .await - .expect("failed to call function"); - - println!("{:?}", output); - - let input3 = RecordBatch::try_new( - Arc::new(input3_schema), - vec![array1.clone(), array2.clone(), array3.clone()], - ) - .unwrap(); - - let output = client - .call("gcd3", input3) - .await - .expect("failed to call function"); - - println!("{:?}", output); -} diff --git a/src/expr/udf/python/.gitignore b/src/expr/udf/python/.gitignore deleted file mode 100644 index 75b18b1dc1919..0000000000000 --- a/src/expr/udf/python/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist -/risingwave.egg-info diff --git a/src/expr/udf/python/CHANGELOG.md b/src/expr/udf/python/CHANGELOG.md deleted file mode 100644 index a20411e69d83e..0000000000000 --- a/src/expr/udf/python/CHANGELOG.md +++ /dev/null @@ -1,37 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.1] - 2023-12-06 - -### Fixed - -- Fix decimal type output. - -## [0.1.0] - 2023-12-01 - -### Fixed - -- Fix unconstrained decimal type. - -## [0.0.12] - 2023-11-28 - -### Changed - -- Change the default struct field name to `f{i}`. - -### Fixed - -- Fix parsing nested struct type. - - -## [0.0.11] - 2023-11-06 - -### Fixed - -- Hook SIGTERM to stop the UDF server gracefully. diff --git a/src/expr/udf/python/README.md b/src/expr/udf/python/README.md deleted file mode 100644 index d1655be05350b..0000000000000 --- a/src/expr/udf/python/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# RisingWave Python UDF SDK - -This library provides a Python SDK for creating user-defined functions (UDF) in [RisingWave](https://www.risingwave.com/). - -For a detailed guide on how to use Python UDF in RisingWave, please refer to [this doc](https://docs.risingwave.com/docs/current/udf-python/). - -## Introduction - -RisingWave supports user-defined functions implemented as external functions. -With the RisingWave Python UDF SDK, users can define custom UDFs using Python and start a Python process as a UDF server. -RisingWave can then remotely access the UDF server to execute the defined functions. - -## Installation - -```sh -pip install risingwave -``` - -## Usage - -Define functions in a Python file: - -```python -# udf.py -from risingwave.udf import udf, udtf, UdfServer -import struct -import socket - -# Define a scalar function -@udf(input_types=['INT', 'INT'], result_type='INT') -def gcd(x, y): - while y != 0: - (x, y) = (y, x % y) - return x - -# Define a scalar function that returns multiple values (within a struct) -@udf(input_types=['BYTEA'], result_type='STRUCT') -def extract_tcp_info(tcp_packet: bytes): - src_addr, dst_addr = struct.unpack('!4s4s', tcp_packet[12:20]) - src_port, dst_port = struct.unpack('!HH', tcp_packet[20:24]) - src_addr = socket.inet_ntoa(src_addr) - dst_addr = socket.inet_ntoa(dst_addr) - return src_addr, dst_addr, src_port, dst_port - -# Define a table function -@udtf(input_types='INT', result_types='INT') -def series(n): - for i in range(n): - yield i - -# Start a UDF server -if __name__ == '__main__': - server = UdfServer(location="0.0.0.0:8815") - server.add_function(gcd) - server.add_function(series) - server.serve() -``` - -Start the UDF server: - -```sh -python3 udf.py -``` - -To create functions in RisingWave, use the following syntax: - -```sql -create function ( [, ...] ) - [ returns | returns table ( [, ...] ) ] - as using link ''; -``` - -- The `as` parameter specifies the function name defined in the UDF server. -- The `link` parameter specifies the address of the UDF server. - -For example: - -```sql -create function gcd(int, int) returns int -as gcd using link 'http://localhost:8815'; - -create function series(int) returns table (x int) -as series using link 'http://localhost:8815'; - -select gcd(25, 15); - -select * from series(10); -``` - -## Data Types - -The RisingWave Python UDF SDK supports the following data types: - -| SQL Type | Python Type | Notes | -| ---------------- | ----------------------------- | ------------------ | -| BOOLEAN | bool | | -| SMALLINT | int | | -| INT | int | | -| BIGINT | int | | -| REAL | float | | -| DOUBLE PRECISION | float | | -| DECIMAL | decimal.Decimal | | -| DATE | datetime.date | | -| TIME | datetime.time | | -| TIMESTAMP | datetime.datetime | | -| INTERVAL | MonthDayNano / (int, int, int) | Fields can be obtained by `months()`, `days()` and `nanoseconds()` from `MonthDayNano` | -| VARCHAR | str | | -| BYTEA | bytes | | -| JSONB | any | | -| T[] | list[T] | | -| STRUCT<> | tuple | | -| ...others | | Not supported yet. | diff --git a/src/expr/udf/python/publish.md b/src/expr/udf/python/publish.md deleted file mode 100644 index 0bc22d713906f..0000000000000 --- a/src/expr/udf/python/publish.md +++ /dev/null @@ -1,19 +0,0 @@ -# How to publish this library - -Install the build tool: - -```sh -pip3 install build -``` - -Build the library: - -```sh -python3 -m build -``` - -Upload the library to PyPI: - -```sh -twine upload dist/* -``` diff --git a/src/expr/udf/python/pyproject.toml b/src/expr/udf/python/pyproject.toml deleted file mode 100644 index b535355168363..0000000000000 --- a/src/expr/udf/python/pyproject.toml +++ /dev/null @@ -1,20 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "risingwave" -version = "0.1.1" -authors = [{ name = "RisingWave Labs" }] -description = "RisingWave Python API" -readme = "README.md" -license = { text = "Apache Software License" } -classifiers = [ - "Programming Language :: Python", - "License :: OSI Approved :: Apache Software License", -] -requires-python = ">=3.8" -dependencies = ["pyarrow"] - -[project.optional-dependencies] -test = ["pytest"] diff --git a/src/expr/udf/python/risingwave/__init__.py b/src/expr/udf/python/risingwave/__init__.py deleted file mode 100644 index 3d60f2f96d025..0000000000000 --- a/src/expr/udf/python/risingwave/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# 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. diff --git a/src/expr/udf/python/risingwave/test_udf.py b/src/expr/udf/python/risingwave/test_udf.py deleted file mode 100644 index e3c2029d3d1f9..0000000000000 --- a/src/expr/udf/python/risingwave/test_udf.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# 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. - -from multiprocessing import Process -import pytest -from risingwave.udf import udf, UdfServer, _to_data_type -import pyarrow as pa -import pyarrow.flight as flight -import time -import datetime -from typing import Any - - -def flight_server(): - server = UdfServer(location="localhost:8815") - server.add_function(add) - server.add_function(wait) - server.add_function(wait_concurrent) - server.add_function(return_all) - return server - - -def flight_client(): - client = flight.FlightClient(("localhost", 8815)) - return client - - -# Define a scalar function -@udf(input_types=["INT", "INT"], result_type="INT") -def add(x, y): - return x + y - - -@udf(input_types=["INT"], result_type="INT") -def wait(x): - time.sleep(0.01) - return 0 - - -@udf(input_types=["INT"], result_type="INT", io_threads=32) -def wait_concurrent(x): - time.sleep(0.01) - return 0 - - -@udf( - input_types=[ - "BOOLEAN", - "SMALLINT", - "INT", - "BIGINT", - "FLOAT4", - "FLOAT8", - "DECIMAL", - "DATE", - "TIME", - "TIMESTAMP", - "INTERVAL", - "VARCHAR", - "BYTEA", - "JSONB", - ], - result_type="""struct< - BOOLEAN, - SMALLINT, - INT, - BIGINT, - FLOAT4, - FLOAT8, - DECIMAL, - DATE, - TIME, - TIMESTAMP, - INTERVAL, - VARCHAR, - BYTEA, - JSONB - >""", -) -def return_all( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, -): - return ( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, - ) - - -def test_simple(): - LEN = 64 - data = pa.Table.from_arrays( - [pa.array(range(0, LEN)), pa.array(range(0, LEN))], names=["x", "y"] - ) - - batches = data.to_batches(max_chunksize=512) - - with flight_client() as client, flight_server() as server: - flight_info = flight.FlightDescriptor.for_path(b"add") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=data.schema) - for batch in batches: - writer.write_batch(batch) - writer.done_writing() - - chunk = reader.read_chunk() - assert len(chunk.data) == LEN - assert chunk.data.column("output").equals( - pa.array(range(0, LEN * 2, 2), type=pa.int32()) - ) - - -def test_io_concurrency(): - LEN = 64 - data = pa.Table.from_arrays([pa.array(range(0, LEN))], names=["x"]) - batches = data.to_batches(max_chunksize=512) - - with flight_client() as client, flight_server() as server: - # Single-threaded function takes a long time - flight_info = flight.FlightDescriptor.for_path(b"wait") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=data.schema) - for batch in batches: - writer.write_batch(batch) - writer.done_writing() - start_time = time.time() - - total_len = 0 - for chunk in reader: - total_len += len(chunk.data) - - assert total_len == LEN - - elapsed_time = time.time() - start_time # ~0.64s - assert elapsed_time > 0.5 - - # Multi-threaded I/O bound function will take a much shorter time - flight_info = flight.FlightDescriptor.for_path(b"wait_concurrent") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=data.schema) - for batch in batches: - writer.write_batch(batch) - writer.done_writing() - start_time = time.time() - - total_len = 0 - for chunk in reader: - total_len += len(chunk.data) - - assert total_len == LEN - - elapsed_time = time.time() - start_time - assert elapsed_time < 0.25 - - -def test_all_types(): - arrays = [ - pa.array([True], type=pa.bool_()), - pa.array([1], type=pa.int16()), - pa.array([1], type=pa.int32()), - pa.array([1], type=pa.int64()), - pa.array([1], type=pa.float32()), - pa.array([1], type=pa.float64()), - pa.array(["12345678901234567890.1234567890"], type=pa.large_binary()), - pa.array([datetime.date(2023, 6, 1)], type=pa.date32()), - pa.array([datetime.time(1, 2, 3, 456789)], type=pa.time64("us")), - pa.array( - [datetime.datetime(2023, 6, 1, 1, 2, 3, 456789)], - type=pa.timestamp("us"), - ), - pa.array([(1, 2, 3)], type=pa.month_day_nano_interval()), - pa.array(["string"], type=pa.string()), - pa.array(["bytes"], type=pa.binary()), - pa.array(['{ "key": 1 }'], type=pa.large_string()), - ] - batch = pa.RecordBatch.from_arrays(arrays, names=["" for _ in arrays]) - - with flight_client() as client, flight_server() as server: - flight_info = flight.FlightDescriptor.for_path(b"return_all") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=batch.schema) - writer.write_batch(batch) - writer.done_writing() - - chunk = reader.read_chunk() - assert [v.as_py() for _, v in chunk.data.column(0)[0].items()] == [ - True, - 1, - 1, - 1, - 1.0, - 1.0, - b"12345678901234567890.1234567890", - datetime.date(2023, 6, 1), - datetime.time(1, 2, 3, 456789), - datetime.datetime(2023, 6, 1, 1, 2, 3, 456789), - (1, 2, 3), - "string", - b"bytes", - '{"key": 1}', - ] diff --git a/src/expr/udf/python/risingwave/udf.py b/src/expr/udf/python/risingwave/udf.py deleted file mode 100644 index aad53e25e0c98..0000000000000 --- a/src/expr/udf/python/risingwave/udf.py +++ /dev/null @@ -1,552 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# 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. - -from typing import * -import pyarrow as pa -import pyarrow.flight -import pyarrow.parquet -import inspect -import traceback -import json -from concurrent.futures import ThreadPoolExecutor -import concurrent -from decimal import Decimal -import signal - - -class UserDefinedFunction: - """ - Base interface for user-defined function. - """ - - _name: str - _input_schema: pa.Schema - _result_schema: pa.Schema - _io_threads: Optional[int] - _executor: Optional[ThreadPoolExecutor] - - def eval_batch(self, batch: pa.RecordBatch) -> Iterator[pa.RecordBatch]: - """ - Apply the function on a batch of inputs. - """ - return iter([]) - - -class ScalarFunction(UserDefinedFunction): - """ - Base interface for user-defined scalar function. A user-defined scalar functions maps zero, one, - or multiple scalar values to a new scalar value. - """ - - def __init__(self, *args, **kwargs): - self._io_threads = kwargs.pop("io_threads") - self._executor = ( - ThreadPoolExecutor(max_workers=self._io_threads) - if self._io_threads is not None - else None - ) - super().__init__(*args, **kwargs) - - def eval(self, *args) -> Any: - """ - Method which defines the logic of the scalar function. - """ - pass - - def eval_batch(self, batch: pa.RecordBatch) -> Iterator[pa.RecordBatch]: - # parse value from json string for jsonb columns - inputs = [[v.as_py() for v in array] for array in batch] - inputs = [ - _process_func(pa.list_(type), False)(array) - for array, type in zip(inputs, self._input_schema.types) - ] - if self._executor is not None: - # evaluate the function for each row - tasks = [ - self._executor.submit(self._func, *[col[i] for col in inputs]) - for i in range(batch.num_rows) - ] - column = [ - future.result() for future in concurrent.futures.as_completed(tasks) - ] - else: - # evaluate the function for each row - column = [ - self.eval(*[col[i] for col in inputs]) for i in range(batch.num_rows) - ] - - column = _process_func(pa.list_(self._result_schema.types[0]), True)(column) - - array = pa.array(column, type=self._result_schema.types[0]) - yield pa.RecordBatch.from_arrays([array], schema=self._result_schema) - - -def _process_func(type: pa.DataType, output: bool) -> Callable: - """Return a function to process input or output value.""" - if pa.types.is_list(type): - func = _process_func(type.value_type, output) - return lambda array: [(func(v) if v is not None else None) for v in array] - - if pa.types.is_struct(type): - funcs = [_process_func(field.type, output) for field in type] - if output: - return lambda tup: tuple( - (func(v) if v is not None else None) for v, func in zip(tup, funcs) - ) - else: - # the input value of struct type is a dict - # we convert it into tuple here - return lambda map: tuple( - (func(v) if v is not None else None) - for v, func in zip(map.values(), funcs) - ) - - if type.equals(JSONB): - if output: - return lambda v: json.dumps(v) - else: - return lambda v: json.loads(v) - - if type.equals(UNCONSTRAINED_DECIMAL): - if output: - - def decimal_to_str(v): - if not isinstance(v, Decimal): - raise ValueError(f"Expected Decimal, got {v}") - # use `f` format to avoid scientific notation, e.g. `1e10` - return format(v, "f").encode("utf-8") - - return decimal_to_str - else: - return lambda v: Decimal(v.decode("utf-8")) - - return lambda v: v - - -class TableFunction(UserDefinedFunction): - """ - Base interface for user-defined table function. A user-defined table functions maps zero, one, - or multiple scalar values to a new table value. - """ - - BATCH_SIZE = 1024 - - def eval(self, *args) -> Iterator: - """ - Method which defines the logic of the table function. - """ - yield - - def eval_batch(self, batch: pa.RecordBatch) -> Iterator[pa.RecordBatch]: - class RecordBatchBuilder: - """A utility class for constructing Arrow RecordBatch by row.""" - - schema: pa.Schema - columns: List[List] - - def __init__(self, schema: pa.Schema): - self.schema = schema - self.columns = [[] for _ in self.schema.types] - - def len(self) -> int: - """Returns the number of rows in the RecordBatch being built.""" - return len(self.columns[0]) - - def append(self, index: int, value: Any): - """Appends a new row to the RecordBatch being built.""" - self.columns[0].append(index) - self.columns[1].append(value) - - def build(self) -> pa.RecordBatch: - """Builds the RecordBatch from the accumulated data and clears the state.""" - # Convert the columns to arrow arrays - arrays = [ - pa.array(col, type) - for col, type in zip(self.columns, self.schema.types) - ] - # Reset columns - self.columns = [[] for _ in self.schema.types] - return pa.RecordBatch.from_arrays(arrays, schema=self.schema) - - builder = RecordBatchBuilder(self._result_schema) - - # Iterate through rows in the input RecordBatch - for row_index in range(batch.num_rows): - row = tuple(column[row_index].as_py() for column in batch) - for result in self.eval(*row): - builder.append(row_index, result) - if builder.len() == self.BATCH_SIZE: - yield builder.build() - if builder.len() != 0: - yield builder.build() - - -class UserDefinedScalarFunctionWrapper(ScalarFunction): - """ - Base Wrapper for Python user-defined scalar function. - """ - - _func: Callable - - def __init__(self, func, input_types, result_type, name=None, io_threads=None): - self._func = func - self._input_schema = pa.schema( - zip( - inspect.getfullargspec(func)[0], - [_to_data_type(t) for t in _to_list(input_types)], - ) - ) - self._result_schema = pa.schema([("output", _to_data_type(result_type))]) - self._name = name or ( - func.__name__ if hasattr(func, "__name__") else func.__class__.__name__ - ) - super().__init__(io_threads=io_threads) - - def __call__(self, *args): - return self._func(*args) - - def eval(self, *args): - return self._func(*args) - - -class UserDefinedTableFunctionWrapper(TableFunction): - """ - Base Wrapper for Python user-defined table function. - """ - - _func: Callable - - def __init__(self, func, input_types, result_types, name=None): - self._func = func - self._name = name or ( - func.__name__ if hasattr(func, "__name__") else func.__class__.__name__ - ) - self._input_schema = pa.schema( - zip( - inspect.getfullargspec(func)[0], - [_to_data_type(t) for t in _to_list(input_types)], - ) - ) - self._result_schema = pa.schema( - [ - ("row_index", pa.int32()), - ( - self._name, - pa.struct([("", _to_data_type(t)) for t in result_types]) - if isinstance(result_types, list) - else _to_data_type(result_types), - ), - ] - ) - - def __call__(self, *args): - return self._func(*args) - - def eval(self, *args): - return self._func(*args) - - -def _to_list(x): - if isinstance(x, list): - return x - else: - return [x] - - -def udf( - input_types: Union[List[Union[str, pa.DataType]], Union[str, pa.DataType]], - result_type: Union[str, pa.DataType], - name: Optional[str] = None, - io_threads: Optional[int] = None, -) -> Callable: - """ - Annotation for creating a user-defined scalar function. - - Parameters: - - input_types: A list of strings or Arrow data types that specifies the input data types. - - result_type: A string or an Arrow data type that specifies the return value type. - - name: An optional string specifying the function name. If not provided, the original name will be used. - - io_threads: Number of I/O threads used per data chunk for I/O bound functions. - - Example: - ``` - @udf(input_types=['INT', 'INT'], result_type='INT') - def gcd(x, y): - while y != 0: - (x, y) = (y, x % y) - return x - ``` - - I/O bound Example: - ``` - @udf(input_types=['INT'], result_type='INT', io_threads=64) - def external_api(x): - response = requests.get(my_endpoint + '?param=' + x) - return response["data"] - ``` - """ - - if io_threads is not None and io_threads > 1: - return lambda f: UserDefinedScalarFunctionWrapper( - f, input_types, result_type, name, io_threads=io_threads - ) - else: - return lambda f: UserDefinedScalarFunctionWrapper( - f, input_types, result_type, name - ) - - -def udtf( - input_types: Union[List[Union[str, pa.DataType]], Union[str, pa.DataType]], - result_types: Union[List[Union[str, pa.DataType]], Union[str, pa.DataType]], - name: Optional[str] = None, -) -> Callable: - """ - Annotation for creating a user-defined table function. - - Parameters: - - input_types: A list of strings or Arrow data types that specifies the input data types. - - result_types A list of strings or Arrow data types that specifies the return value types. - - name: An optional string specifying the function name. If not provided, the original name will be used. - - Example: - ``` - @udtf(input_types='INT', result_types='INT') - def series(n): - for i in range(n): - yield i - ``` - """ - - return lambda f: UserDefinedTableFunctionWrapper(f, input_types, result_types, name) - - -class UdfServer(pa.flight.FlightServerBase): - """ - A server that provides user-defined functions to clients. - - Example: - ``` - server = UdfServer(location="0.0.0.0:8815") - server.add_function(my_udf) - server.serve() - ``` - """ - - # UDF server based on Apache Arrow Flight protocol. - # Reference: https://arrow.apache.org/cookbook/py/flight.html#simple-parquet-storage-service-with-arrow-flight - - _location: str - _functions: Dict[str, UserDefinedFunction] - - def __init__(self, location="0.0.0.0:8815", **kwargs): - super(UdfServer, self).__init__("grpc://" + location, **kwargs) - self._location = location - self._functions = {} - - def get_flight_info(self, context, descriptor): - """Return the result schema of a function.""" - udf = self._functions[descriptor.path[0].decode("utf-8")] - # return the concatenation of input and output schema - full_schema = pa.schema(list(udf._input_schema) + list(udf._result_schema)) - # we use `total_records` to indicate the number of input arguments - return pa.flight.FlightInfo( - schema=full_schema, - descriptor=descriptor, - endpoints=[], - total_records=len(udf._input_schema), - total_bytes=0, - ) - - def add_function(self, udf: UserDefinedFunction): - """Add a function to the server.""" - name = udf._name - if name in self._functions: - raise ValueError("Function already exists: " + name) - - input_types = ",".join( - [_data_type_to_string(t) for t in udf._input_schema.types] - ) - if isinstance(udf, TableFunction): - output_type = udf._result_schema.types[-1] - if isinstance(output_type, pa.StructType): - output_type = ",".join( - f"field_{i} {_data_type_to_string(field.type)}" - for i, field in enumerate(output_type) - ) - output_type = f"TABLE({output_type})" - else: - output_type = _data_type_to_string(output_type) - output_type = f"TABLE(output {output_type})" - else: - output_type = _data_type_to_string(udf._result_schema.types[-1]) - - sql = f"CREATE FUNCTION {name}({input_types}) RETURNS {output_type} AS '{name}' USING LINK 'http://{self._location}';" - print(f"added function: {name}, corresponding SQL:\n{sql}\n") - self._functions[name] = udf - - def do_exchange(self, context, descriptor, reader, writer): - """Call a function from the client.""" - udf = self._functions[descriptor.path[0].decode("utf-8")] - writer.begin(udf._result_schema) - try: - for batch in reader: - # print(pa.Table.from_batches([batch.data])) - for output_batch in udf.eval_batch(batch.data): - writer.write_batch(output_batch) - except Exception as e: - print(traceback.print_exc()) - raise e - - def serve(self): - """ - Block until the server shuts down. - - This method only returns if shutdown() is called or a signal (SIGINT, SIGTERM) received. - """ - print( - "Note: You can use arbitrary function names and struct field names in CREATE FUNCTION statements." - f"\n\nlistening on {self._location}" - ) - signal.signal(signal.SIGTERM, lambda s, f: self.shutdown()) - super(UdfServer, self).serve() - - -def _to_data_type(t: Union[str, pa.DataType]) -> pa.DataType: - """ - Convert a SQL data type string or `pyarrow.DataType` to `pyarrow.DataType`. - """ - if isinstance(t, str): - return _string_to_data_type(t) - else: - return t - - -# we use `large_binary` to represent unconstrained decimal type -UNCONSTRAINED_DECIMAL = pa.large_binary() -JSONB = pa.large_string() - - -def _string_to_data_type(type_str: str): - """ - Convert a SQL data type string to `pyarrow.DataType`. - """ - type_str = type_str.upper() - if type_str.endswith("[]"): - return pa.list_(_string_to_data_type(type_str[:-2])) - elif type_str in ("BOOLEAN", "BOOL"): - return pa.bool_() - elif type_str in ("SMALLINT", "INT2"): - return pa.int16() - elif type_str in ("INT", "INTEGER", "INT4"): - return pa.int32() - elif type_str in ("BIGINT", "INT8"): - return pa.int64() - elif type_str in ("FLOAT4", "REAL"): - return pa.float32() - elif type_str in ("FLOAT8", "DOUBLE PRECISION"): - return pa.float64() - elif type_str.startswith("DECIMAL") or type_str.startswith("NUMERIC"): - if type_str == "DECIMAL" or type_str == "NUMERIC": - return UNCONSTRAINED_DECIMAL - rest = type_str[8:-1] # remove "DECIMAL(" and ")" - if "," in rest: - precision, scale = rest.split(",") - return pa.decimal128(int(precision), int(scale)) - else: - return pa.decimal128(int(rest), 0) - elif type_str in ("DATE"): - return pa.date32() - elif type_str in ("TIME", "TIME WITHOUT TIME ZONE"): - return pa.time64("us") - elif type_str in ("TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"): - return pa.timestamp("us") - elif type_str.startswith("INTERVAL"): - return pa.month_day_nano_interval() - elif type_str in ("VARCHAR"): - return pa.string() - elif type_str in ("JSONB"): - return JSONB - elif type_str in ("BYTEA"): - return pa.binary() - elif type_str.startswith("STRUCT"): - # extract 'STRUCT, ...>' - type_list = type_str[7:-1] # strip "STRUCT<>" - fields = [] - elements = [] - start = 0 - depth = 0 - for i, c in enumerate(type_list): - if c == "<": - depth += 1 - elif c == ">": - depth -= 1 - elif c == "," and depth == 0: - type_str = type_list[start:i].strip() - fields.append(pa.field("", _string_to_data_type(type_str))) - start = i + 1 - type_str = type_list[start:].strip() - fields.append(pa.field("", _string_to_data_type(type_str))) - return pa.struct(fields) - - raise ValueError(f"Unsupported type: {type_str}") - - -def _data_type_to_string(t: pa.DataType) -> str: - """ - Convert a `pyarrow.DataType` to a SQL data type string. - """ - if isinstance(t, pa.ListType): - return _data_type_to_string(t.value_type) + "[]" - elif t.equals(pa.bool_()): - return "BOOLEAN" - elif t.equals(pa.int16()): - return "SMALLINT" - elif t.equals(pa.int32()): - return "INT" - elif t.equals(pa.int64()): - return "BIGINT" - elif t.equals(pa.float32()): - return "FLOAT4" - elif t.equals(pa.float64()): - return "FLOAT8" - elif t.equals(UNCONSTRAINED_DECIMAL): - return "DECIMAL" - elif pa.types.is_decimal(t): - return f"DECIMAL({t.precision},{t.scale})" - elif t.equals(pa.date32()): - return "DATE" - elif t.equals(pa.time64("us")): - return "TIME" - elif t.equals(pa.timestamp("us")): - return "TIMESTAMP" - elif t.equals(pa.month_day_nano_interval()): - return "INTERVAL" - elif t.equals(pa.string()): - return "VARCHAR" - elif t.equals(JSONB): - return "JSONB" - elif t.equals(pa.binary()): - return "BYTEA" - elif isinstance(t, pa.StructType): - return ( - "STRUCT<" - + ",".join( - f"f{i+1} {_data_type_to_string(field.type)}" - for i, field in enumerate(t) - ) - + ">" - ) - else: - raise ValueError(f"Unsupported type: {t}") diff --git a/src/expr/udf/python/risingwave/udf/health_check.py b/src/expr/udf/python/risingwave/udf/health_check.py deleted file mode 100644 index ad2d38681a6cc..0000000000000 --- a/src/expr/udf/python/risingwave/udf/health_check.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# 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. - -from pyarrow.flight import FlightClient -import sys - - -def check_udf_service_available(addr: str) -> bool: - """Check if the UDF service is available at the given address.""" - try: - client = FlightClient(f"grpc://{addr}") - client.wait_for_available() - return True - except Exception as e: - print(f"Error connecting to RisingWave UDF service: {str(e)}") - return False - - -if __name__ == "__main__": - if len(sys.argv) != 2: - print("usage: python3 health_check.py ") - sys.exit(1) - - server_address = sys.argv[1] - if check_udf_service_available(server_address): - print("OK") - else: - print("unavailable") - exit(-1) diff --git a/src/expr/udf/src/error.rs b/src/expr/udf/src/error.rs deleted file mode 100644 index fc6733052b137..0000000000000 --- a/src/expr/udf/src/error.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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::error::FlightError; -use thiserror::Error; -use thiserror_ext::{Box, Construct}; - -/// A specialized `Result` type for UDF operations. -pub type Result = std::result::Result; - -/// The error type for UDF operations. -#[derive(Error, Debug, Box, Construct)] -#[thiserror_ext(newtype(name = Error))] -pub enum ErrorInner { - #[error("failed to send requests to UDF service: {0}")] - Tonic(#[from] tonic::Status), - - #[error("failed to call UDF: {0}")] - Flight(#[from] FlightError), - - #[error("type mismatch: {0}")] - TypeMismatch(String), - - #[error("arrow error: {0}")] - Arrow(#[from] arrow_schema::ArrowError), - - #[error("UDF unsupported: {0}")] - // TODO(error-handling): should prefer use error types than strings. - Unsupported(String), - - #[error("UDF service returned no data")] - NoReturned, - - #[error("Flight service error: {0}")] - ServiceError(String), -} - -impl Error { - /// Returns true if the error is caused by a connection error. - pub fn is_connection_error(&self) -> bool { - match self.inner() { - // Connection refused - ErrorInner::Tonic(status) if status.code() == tonic::Code::Unavailable => true, - _ => false, - } - } - - pub fn is_tonic_error(&self) -> bool { - matches!( - self.inner(), - ErrorInner::Tonic(_) | ErrorInner::Flight(FlightError::Tonic(_)) - ) - } -} - -static_assertions::const_assert_eq!(std::mem::size_of::(), 8); diff --git a/src/expr/udf/src/external.rs b/src/expr/udf/src/external.rs deleted file mode 100644 index 7560638b03985..0000000000000 --- a/src/expr/udf/src/external.rs +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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::str::FromStr; -use std::time::Duration; - -use arrow_array::RecordBatch; -use arrow_flight::decode::FlightRecordBatchStream; -use arrow_flight::encode::FlightDataEncoderBuilder; -use arrow_flight::error::FlightError; -use arrow_flight::flight_service_client::FlightServiceClient; -use arrow_flight::{FlightData, FlightDescriptor}; -use arrow_schema::Schema; -use cfg_or_panic::cfg_or_panic; -use futures_util::{stream, FutureExt, Stream, StreamExt, TryStreamExt}; -use ginepro::{LoadBalancedChannel, ResolutionStrategy}; -use risingwave_common::util::addr::HostAddr; -use thiserror_ext::AsReport; -use tokio::time::Duration as TokioDuration; -use tonic::transport::Channel; - -use crate::metrics::GLOBAL_METRICS; -use crate::{Error, Result}; - -// Interval between two successive probes of the UDF DNS. -const DNS_PROBE_INTERVAL_SECS: u64 = 5; -// Timeout duration for performing an eager DNS resolution. -const EAGER_DNS_RESOLVE_TIMEOUT_SECS: u64 = 5; -const REQUEST_TIMEOUT_SECS: u64 = 5; -const CONNECT_TIMEOUT_SECS: u64 = 5; - -/// Client for external function service based on Arrow Flight. -#[derive(Debug)] -pub struct ArrowFlightUdfClient { - client: FlightServiceClient, - addr: String, -} - -// TODO: support UDF in simulation -#[cfg_or_panic(not(madsim))] -impl ArrowFlightUdfClient { - /// Connect to a UDF service. - pub async fn connect(addr: &str) -> Result { - Self::connect_inner( - addr, - ResolutionStrategy::Eager { - timeout: TokioDuration::from_secs(EAGER_DNS_RESOLVE_TIMEOUT_SECS), - }, - ) - .await - } - - /// Connect to a UDF service lazily (i.e. only when the first request is sent). - pub fn connect_lazy(addr: &str) -> Result { - Self::connect_inner(addr, ResolutionStrategy::Lazy) - .now_or_never() - .unwrap() - } - - async fn connect_inner( - mut addr: &str, - resolution_strategy: ResolutionStrategy, - ) -> Result { - if addr.starts_with("http://") { - addr = addr.strip_prefix("http://").unwrap(); - } - if addr.starts_with("https://") { - addr = addr.strip_prefix("https://").unwrap(); - } - let host_addr = HostAddr::from_str(addr).map_err(|e| { - Error::service_error(format!("invalid address: {}, err: {}", addr, e.as_report())) - })?; - let channel = LoadBalancedChannel::builder((host_addr.host.clone(), host_addr.port)) - .dns_probe_interval(std::time::Duration::from_secs(DNS_PROBE_INTERVAL_SECS)) - .timeout(Duration::from_secs(REQUEST_TIMEOUT_SECS)) - .connect_timeout(Duration::from_secs(CONNECT_TIMEOUT_SECS)) - .resolution_strategy(resolution_strategy) - .channel() - .await - .map_err(|e| { - Error::service_error(format!( - "failed to create LoadBalancedChannel, address: {}, err: {}", - host_addr, - e.as_report() - )) - })?; - let client = FlightServiceClient::new(channel.into()); - Ok(Self { - client, - addr: addr.into(), - }) - } - - /// Check if the function is available and the schema is match. - pub async fn check(&self, id: &str, args: &Schema, returns: &Schema) -> Result<()> { - let descriptor = FlightDescriptor::new_path(vec![id.into()]); - - let response = self.client.clone().get_flight_info(descriptor).await?; - - // check schema - let info = response.into_inner(); - let input_num = info.total_records as usize; - let full_schema = Schema::try_from(info).map_err(|e| { - FlightError::DecodeError(format!("Error decoding schema: {}", e.as_report())) - })?; - if input_num > full_schema.fields.len() { - return Err(Error::service_error(format!( - "function {:?} schema info not consistency: input_num: {}, total_fields: {}", - id, - input_num, - full_schema.fields.len() - ))); - } - - let (input_fields, return_fields) = full_schema.fields.split_at(input_num); - let actual_input_types: Vec<_> = input_fields.iter().map(|f| f.data_type()).collect(); - let actual_result_types: Vec<_> = return_fields.iter().map(|f| f.data_type()).collect(); - let expect_input_types: Vec<_> = args.fields.iter().map(|f| f.data_type()).collect(); - let expect_result_types: Vec<_> = returns.fields.iter().map(|f| f.data_type()).collect(); - if !data_types_match(&expect_input_types, &actual_input_types) { - return Err(Error::type_mismatch(format!( - "function: {:?}, expect arguments: {:?}, actual: {:?}", - id, expect_input_types, actual_input_types - ))); - } - if !data_types_match(&expect_result_types, &actual_result_types) { - return Err(Error::type_mismatch(format!( - "function: {:?}, expect return: {:?}, actual: {:?}", - id, expect_result_types, actual_result_types - ))); - } - Ok(()) - } - - /// Call a function. - pub async fn call(&self, id: &str, input: RecordBatch) -> Result { - self.call_internal(id, input).await - } - - async fn call_internal(&self, id: &str, input: RecordBatch) -> Result { - let mut output_stream = self - .call_stream_internal(id, stream::once(async { input })) - .await?; - let mut batches = vec![]; - while let Some(batch) = output_stream.next().await { - batches.push(batch?); - } - Ok(arrow_select::concat::concat_batches( - output_stream.schema().ok_or_else(Error::no_returned)?, - batches.iter(), - )?) - } - - /// Call a function, retry up to 5 times / 3s if connection is broken. - pub async fn call_with_retry(&self, id: &str, input: RecordBatch) -> Result { - let mut backoff = Duration::from_millis(100); - for i in 0..5 { - match self.call(id, input.clone()).await { - Err(err) if err.is_connection_error() && i != 4 => { - tracing::error!(error = %err.as_report(), "UDF connection error. retry..."); - } - ret => return ret, - } - tokio::time::sleep(backoff).await; - backoff *= 2; - } - unreachable!() - } - - /// Always retry on connection error - pub async fn call_with_always_retry_on_network_error( - &self, - id: &str, - input: RecordBatch, - fragment_id: &str, - ) -> Result { - let mut backoff = Duration::from_millis(100); - let metrics = &*GLOBAL_METRICS; - let labels: &[&str; 4] = &[&self.addr, "external", id, fragment_id]; - loop { - match self.call(id, input.clone()).await { - Err(err) if err.is_tonic_error() => { - tracing::error!(error = %err.as_report(), "UDF tonic error. retry..."); - } - ret => { - if ret.is_err() { - tracing::error!(error = %ret.as_ref().unwrap_err().as_report(), "UDF error. exiting..."); - } - return ret; - } - } - metrics.udf_retry_count.with_label_values(labels).inc(); - tokio::time::sleep(backoff).await; - backoff *= 2; - } - } - - /// Call a function with streaming input and output. - #[panic_return = "Result>"] - pub async fn call_stream( - &self, - id: &str, - inputs: impl Stream + Send + 'static, - ) -> Result> + Send + 'static> { - Ok(self - .call_stream_internal(id, inputs) - .await? - .map_err(|e| e.into())) - } - - async fn call_stream_internal( - &self, - id: &str, - inputs: impl Stream + Send + 'static, - ) -> Result { - let descriptor = FlightDescriptor::new_path(vec![id.into()]); - let flight_data_stream = - FlightDataEncoderBuilder::new() - .build(inputs.map(Ok)) - .map(move |res| FlightData { - // TODO: fill descriptor only for the first message - flight_descriptor: Some(descriptor.clone()), - ..res.unwrap() - }); - - // call `do_exchange` on Flight server - let response = self.client.clone().do_exchange(flight_data_stream).await?; - - // decode response - let stream = response.into_inner(); - Ok(FlightRecordBatchStream::new_from_flight_data( - // convert tonic::Status to FlightError - stream.map_err(|e| e.into()), - )) - } - - pub fn get_addr(&self) -> &str { - &self.addr - } -} - -/// Check if two list of data types match, ignoring field names. -fn data_types_match(a: &[&arrow_schema::DataType], b: &[&arrow_schema::DataType]) -> bool { - if a.len() != b.len() { - return false; - } - #[allow(clippy::disallowed_methods)] - a.iter().zip(b.iter()).all(|(a, b)| a.equals_datatype(b)) -} diff --git a/src/expr/udf/src/metrics.rs b/src/expr/udf/src/metrics.rs deleted file mode 100644 index 50ef1b068307d..0000000000000 --- a/src/expr/udf/src/metrics.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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::LazyLock; - -use prometheus::{ - exponential_buckets, register_histogram_vec_with_registry, - register_int_counter_vec_with_registry, HistogramVec, IntCounterVec, Registry, -}; -use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY; - -/// Monitor metrics for UDF. -#[derive(Debug, Clone)] -pub struct Metrics { - /// Number of successful UDF calls. - pub udf_success_count: IntCounterVec, - /// Number of failed UDF calls. - pub udf_failure_count: IntCounterVec, - /// Total number of retried UDF calls. - pub udf_retry_count: IntCounterVec, - /// Input chunk rows of UDF calls. - pub udf_input_chunk_rows: HistogramVec, - /// The latency of UDF calls in seconds. - pub udf_latency: HistogramVec, - /// Total number of input rows of UDF calls. - pub udf_input_rows: IntCounterVec, - /// Total number of input bytes of UDF calls. - pub udf_input_bytes: IntCounterVec, -} - -/// Global UDF metrics. -pub static GLOBAL_METRICS: LazyLock = - LazyLock::new(|| Metrics::new(&GLOBAL_METRICS_REGISTRY)); - -impl Metrics { - fn new(registry: &Registry) -> Self { - let labels = &["link", "language", "name", "fragment_id"]; - let udf_success_count = register_int_counter_vec_with_registry!( - "udf_success_count", - "Total number of successful UDF calls", - labels, - registry - ) - .unwrap(); - let udf_failure_count = register_int_counter_vec_with_registry!( - "udf_failure_count", - "Total number of failed UDF calls", - labels, - registry - ) - .unwrap(); - let udf_retry_count = register_int_counter_vec_with_registry!( - "udf_retry_count", - "Total number of retried UDF calls", - labels, - registry - ) - .unwrap(); - let udf_input_chunk_rows = register_histogram_vec_with_registry!( - "udf_input_chunk_rows", - "Input chunk rows of UDF calls", - labels, - exponential_buckets(1.0, 2.0, 10).unwrap(), // 1 to 1024 - registry - ) - .unwrap(); - let udf_latency = register_histogram_vec_with_registry!( - "udf_latency", - "The latency(s) of UDF calls", - labels, - exponential_buckets(0.000001, 2.0, 30).unwrap(), // 1us to 1000s - registry - ) - .unwrap(); - let udf_input_rows = register_int_counter_vec_with_registry!( - "udf_input_rows", - "Total number of input rows of UDF calls", - labels, - registry - ) - .unwrap(); - let udf_input_bytes = register_int_counter_vec_with_registry!( - "udf_input_bytes", - "Total number of input bytes of UDF calls", - labels, - registry - ) - .unwrap(); - - Metrics { - udf_success_count, - udf_failure_count, - udf_retry_count, - udf_input_chunk_rows, - udf_latency, - udf_input_rows, - udf_input_bytes, - } - } -} diff --git a/src/frontend/Cargo.toml b/src/frontend/Cargo.toml index 3cba7afe82660..418ab86e43075 100644 --- a/src/frontend/Cargo.toml +++ b/src/frontend/Cargo.toml @@ -18,7 +18,7 @@ normal = ["workspace-hack"] anyhow = "1" arc-swap = "1" arrow-schema = { workspace = true } -arrow-udf-wasm = { workspace = true } +arrow-schema-iceberg = { workspace = true } async-recursion = "1.1.0" async-trait = "0.1" auto_enums = { workspace = true } @@ -30,7 +30,7 @@ clap = { workspace = true } downcast-rs = "1.2" dyn-clone = "1.0.14" easy-ext = "1" -educe = "0.5" +educe = "0.6" either = "1" enum-as-inner = "0.6" fancy-regex = "0.13.0" @@ -38,9 +38,10 @@ fixedbitset = "0.5" futures = { version = "0.3", default-features = false, features = ["alloc"] } futures-async-stream = { workspace = true } iana-time-zone = "0.1" +iceberg = { workspace = true } icelake = { workspace = true } itertools = { workspace = true } -jsonbb = "0.1.2" +jsonbb = { workspace = true } linkme = { version = "0.3", features = ["used_linker"] } maplit = "1" md5 = "0.7.0" @@ -72,7 +73,6 @@ risingwave_pb = { workspace = true } risingwave_rpc_client = { workspace = true } risingwave_sqlparser = { workspace = true } risingwave_storage = { workspace = true } -risingwave_udf = { workspace = true } risingwave_variables = { workspace = true } rw_futures_util = { workspace = true } serde = { version = "1", features = ["derive"] } diff --git a/src/frontend/planner_test/src/lib.rs b/src/frontend/planner_test/src/lib.rs index ca44fb2b76e8c..ee4f942f09889 100644 --- a/src/frontend/planner_test/src/lib.rs +++ b/src/frontend/planner_test/src/lib.rs @@ -21,7 +21,7 @@ risingwave_expr_impl::enable!(); mod resolve_id; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -568,9 +568,15 @@ impl TestCase { Statement::CreateSchema { schema_name, if_not_exists, + user_specified, } => { - create_schema::handle_create_schema(handler_args, schema_name, if_not_exists) - .await?; + create_schema::handle_create_schema( + handler_args, + schema_name, + if_not_exists, + user_specified, + ) + .await?; } _ => return Err(anyhow!("Unsupported statement type")), } @@ -599,13 +605,13 @@ impl TestCase { let mut planner = Planner::new(context.clone()); - let mut logical_plan = match planner.plan(bound) { - Ok(logical_plan) => { + let plan_root = match planner.plan(bound) { + Ok(plan_root) => { if self.expected_outputs.contains(&TestType::LogicalPlan) { ret.logical_plan = - Some(explain_plan(&logical_plan.clone().into_unordered_subplan())); + Some(explain_plan(&plan_root.clone().into_unordered_subplan())); } - logical_plan + plan_root } Err(err) => { ret.planner_error = Some(err.to_report_string_pretty()); @@ -618,8 +624,9 @@ impl TestCase { .contains(&TestType::OptimizedLogicalPlanForBatch) || self.expected_outputs.contains(&TestType::OptimizerError) { + let mut plan_root = plan_root.clone(); let optimized_logical_plan_for_batch = - match logical_plan.gen_optimized_logical_plan_for_batch() { + match plan_root.gen_optimized_logical_plan_for_batch() { Ok(optimized_logical_plan_for_batch) => optimized_logical_plan_for_batch, Err(err) => { ret.optimizer_error = Some(err.to_report_string_pretty()); @@ -642,8 +649,9 @@ impl TestCase { .contains(&TestType::OptimizedLogicalPlanForStream) || self.expected_outputs.contains(&TestType::OptimizerError) { + let mut plan_root = plan_root.clone(); let optimized_logical_plan_for_stream = - match logical_plan.gen_optimized_logical_plan_for_stream() { + match plan_root.gen_optimized_logical_plan_for_stream() { Ok(optimized_logical_plan_for_stream) => optimized_logical_plan_for_stream, Err(err) => { ret.optimizer_error = Some(err.to_report_string_pretty()); @@ -662,15 +670,13 @@ impl TestCase { } 'batch: { - // if self.batch_plan.is_some() - // || self.batch_plan_proto.is_some() - // || self.batch_error.is_some() if self.expected_outputs.contains(&TestType::BatchPlan) || self.expected_outputs.contains(&TestType::BatchPlanProto) || self.expected_outputs.contains(&TestType::BatchError) { - let batch_plan = match logical_plan.gen_batch_plan() { - Ok(batch_plan) => match logical_plan.gen_batch_distributed_plan(batch_plan) { + let mut plan_root = plan_root.clone(); + let batch_plan = match plan_root.gen_batch_plan() { + Ok(_batch_plan) => match plan_root.gen_batch_distributed_plan() { Ok(batch_plan) => batch_plan, Err(err) => { ret.batch_error = Some(err.to_report_string_pretty()); @@ -701,8 +707,9 @@ impl TestCase { if self.expected_outputs.contains(&TestType::BatchLocalPlan) || self.expected_outputs.contains(&TestType::BatchError) { - let batch_plan = match logical_plan.gen_batch_plan() { - Ok(batch_plan) => match logical_plan.gen_batch_local_plan(batch_plan) { + let mut plan_root = plan_root.clone(); + let batch_plan = match plan_root.gen_batch_plan() { + Ok(_batch_plan) => match plan_root.gen_batch_local_plan() { Ok(batch_plan) => batch_plan, Err(err) => { ret.batch_error = Some(err.to_report_string_pretty()); @@ -728,8 +735,9 @@ impl TestCase { .contains(&TestType::BatchDistributedPlan) || self.expected_outputs.contains(&TestType::BatchError) { - let batch_plan = match logical_plan.gen_batch_plan() { - Ok(batch_plan) => match logical_plan.gen_batch_distributed_plan(batch_plan) { + let mut plan_root = plan_root.clone(); + let batch_plan = match plan_root.gen_batch_plan() { + Ok(_batch_plan) => match plan_root.gen_batch_distributed_plan() { Ok(batch_plan) => batch_plan, Err(err) => { ret.batch_error = Some(err.to_report_string_pretty()); @@ -823,13 +831,14 @@ impl TestCase { 'sink: { if self.expected_outputs.contains(&TestType::SinkPlan) { + let mut plan_root = plan_root.clone(); let sink_name = "sink_test"; - let mut options = HashMap::new(); + let mut options = BTreeMap::new(); options.insert("connector".to_string(), "blackhole".to_string()); options.insert("type".to_string(), "append-only".to_string()); let options = WithOptions::new(options); let format_desc = (&options).try_into().unwrap(); - match logical_plan.gen_sink_plan( + match plan_root.gen_sink_plan( sink_name.to_string(), format!("CREATE SINK {sink_name} AS {}", stmt), options, diff --git a/src/frontend/planner_test/tests/testdata/input/agg.yaml b/src/frontend/planner_test/tests/testdata/input/agg.yaml index 154680c8acbc8..70f83549ff18c 100644 --- a/src/frontend/planner_test/tests/testdata/input/agg.yaml +++ b/src/frontend/planner_test/tests/testdata/input/agg.yaml @@ -281,12 +281,14 @@ select distinct v1 from t; expected_outputs: - logical_plan + - stream_plan - name: distinct with agg sql: | create table t (v1 int, v2 int); select distinct sum(v1) from t group by v2; expected_outputs: - logical_plan + - stream_plan - name: distinct on sql: | create table t (v1 int, v2 int, v3 int); diff --git a/src/frontend/planner_test/tests/testdata/input/create_source.yaml b/src/frontend/planner_test/tests/testdata/input/create_source.yaml index be313f83b14da..9d57c828c41d3 100644 --- a/src/frontend/planner_test/tests/testdata/input/create_source.yaml +++ b/src/frontend/planner_test/tests/testdata/input/create_source.yaml @@ -13,7 +13,7 @@ create source s with(connector='kafka') FORMAT PLAIN ENCODE JSON; expected_outputs: - planner_error -- id: csv_delimiter_tab +- id: csv_delimiter_comma sql: | explain create table s0 (v1 int, v2 varchar) with ( connector = 'kafka', @@ -23,6 +23,16 @@ ) FORMAT PLAIN ENCODE CSV (delimiter = ',', without_header = true); expected_outputs: - explain_output +- id: csv_delimiter_semicolon + sql: | + explain create table s0 (v1 int, v2 varchar) with ( + connector = 'kafka', + topic = 'kafka_1_csv_topic', + properties.bootstrap.server = 'message_queue:29092', + scan.startup.mode = 'earliest' + ) FORMAT PLAIN ENCODE CSV (delimiter = ';', without_header = true); + expected_outputs: + - explain_output - id: csv_delimiter_tab sql: | explain create table s0 (v1 int, v2 varchar) with ( diff --git a/src/frontend/planner_test/tests/testdata/input/expr.yaml b/src/frontend/planner_test/tests/testdata/input/expr.yaml index e19ee4a29126b..42664321bfa37 100644 --- a/src/frontend/planner_test/tests/testdata/input/expr.yaml +++ b/src/frontend/planner_test/tests/testdata/input/expr.yaml @@ -443,6 +443,11 @@ select * from max(); expected_outputs: - binder_error +- sql: | + create table t (k int, j jsonb); + select a * k + b from t, jsonb_populate_record(null::struct, j); + expected_outputs: + - batch_plan - name: Grafana issue-10134 sql: | SELECT * FROM diff --git a/src/frontend/planner_test/tests/testdata/input/generate_series_with_now.yaml b/src/frontend/planner_test/tests/testdata/input/generate_series_with_now.yaml new file mode 100644 index 0000000000000..38e98be8cd7c6 --- /dev/null +++ b/src/frontend/planner_test/tests/testdata/input/generate_series_with_now.yaml @@ -0,0 +1,54 @@ +- sql: | + select * from generate_series( + '2024-06-21 17:36:00'::timestamptz, + now(), + interval '1 hour' + ); + expected_outputs: + - logical_plan + - optimized_logical_plan_for_stream + - stream_plan +- sql: | + select * from generate_series( + '2024-06-21 17:36:00'::timestamp, -- `timestamp` type is not supported + now(), + interval '1 hour' + ); + expected_outputs: + - binder_error +- sql: | + select * from generate_series( + now() - interval '1 hour', + now(), + interval '1 hour' + ); + expected_outputs: + - stream_error +- sql: | + select * from unnest(array[now(), now()]); + expected_outputs: + - stream_error +- sql: | + select 1::int as constant from generate_series( + '2024-06-21 17:36:00'::timestamptz, + now(), + interval '1 hour' + ); + expected_outputs: + - logical_plan + - optimized_logical_plan_for_stream + - stream_plan +- sql: | + select + extract('year' from t) as year, + extract('month' from t) as month, + 1::int as constant + from generate_series( + '2024-01-01 00:00:00+00'::timestamptz, + now(), + interval '1 month' + ) as s(t); + expected_outputs: + - logical_plan + - optimized_logical_plan_for_stream + - stream_plan diff --git a/src/frontend/planner_test/tests/testdata/input/index_selection.yaml b/src/frontend/planner_test/tests/testdata/input/index_selection.yaml index 22343381dd926..95495a75fcaf4 100644 --- a/src/frontend/planner_test/tests/testdata/input/index_selection.yaml +++ b/src/frontend/planner_test/tests/testdata/input/index_selection.yaml @@ -453,3 +453,10 @@ select ts::timestamptz from t where ts > now(); expected_outputs: - batch_plan +- name: funtional index with timezone2 + sql: | + create table t (a int, ts timestamp without time zone); + create index idx on t (CAST(ts AS timestamptz)); + select * from t order by ts; + expected_outputs: + - batch_plan diff --git a/src/frontend/planner_test/tests/testdata/input/lateral_subquery.yaml b/src/frontend/planner_test/tests/testdata/input/lateral_subquery.yaml index 869bbdf6d7136..8b9126f18d641 100644 --- a/src/frontend/planner_test/tests/testdata/input/lateral_subquery.yaml +++ b/src/frontend/planner_test/tests/testdata/input/lateral_subquery.yaml @@ -119,3 +119,18 @@ ) AS b ON TRUE; expected_outputs: - stream_plan +- name: https://github.com/risingwavelabs/risingwave/issues/17382 + sql: | + CREATE TABLE r(ts TIMESTAMPTZ, src_id int, dev_id int); + SELECT e.ts AS e_ts, d.* + FROM ( + SELECT '2024-06-20T19:01:00Z'::TIMESTAMPTZ ts, 1::INT AS src_id) e + JOIN LATERAL + ( + SELECT DISTINCT ON(src_id, dev_id) * + FROM r + WHERE r.src_id = e.src_id AND r.ts <= e.ts + ORDER BY src_id, dev_id, ts DESC + )d on true; + expected_outputs: + - batch_plan diff --git a/src/frontend/planner_test/tests/testdata/input/over_window_function.yaml b/src/frontend/planner_test/tests/testdata/input/over_window_function.yaml index 4406089b273b8..09f9c1aa6ecc4 100644 --- a/src/frontend/planner_test/tests/testdata/input/over_window_function.yaml +++ b/src/frontend/planner_test/tests/testdata/input/over_window_function.yaml @@ -591,3 +591,53 @@ from t; expected_outputs: - binder_error + +# Session frames +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + first_value(i) over (partition by bi order by i session with gap 10) as col1, + first_value(bi) over (partition by i order by bi session with gap 10) as col2, + first_value(i) over (partition by bi order by d session with gap 1.5) as col3, + first_value(i) over (partition by bi order by f session with gap 1.5) as col4, + -- first_value(i) over (partition by bi order by da session with gap '1 day') as col5, -- `date` not supported yet + -- first_value(i) over (partition by bi order by t session with gap '1 min') as col6, -- `time` not supported yet + first_value(i) over (partition by bi order by ts session with gap '1 day 1 hour') as col7, + first_value(i) over (partition by bi order by tstz session with gap '1 min') as col8 + from t; + expected_outputs: + - logical_plan + - optimized_logical_plan_for_stream + - stream_error # not supported yet + - batch_plan +- sql: | + create table t (i int, bi bigint, ts timestamp, watermark for ts as ts - interval '1 minute') append only; + select + first_value(i) over (partition by bi order by ts session with gap '10 minutes') as window_start, + last_value(i) over (partition by bi order by ts session with gap '10 minutes') as window_end + from t; + expected_outputs: + - logical_plan + - eowc_stream_plan + - batch_plan +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + count(*) over (partition by 1::int order by da session with gap '1 day') -- `date` not supported yet + from t; + expected_outputs: + - binder_error +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + count(*) over (partition by 1::int order by t session with gap '1 min') -- `time` not supported yet + from t; + expected_outputs: + - binder_error +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + count(*) over (partition by 1::int order by tstz session with gap '1 day 1 hour') -- `timestamptz` +/- 'x month x day' not supported yet + from t; + expected_outputs: + - binder_error diff --git a/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml b/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml index 411d129d415ff..bdd6f11f0b7be 100644 --- a/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml +++ b/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml @@ -1,12 +1,16 @@ - name: basic sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT * FROM t1; expected_outputs: - - planner_error + - logical_plan - name: output column follows lhs sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a FROM t1; expected_outputs: - - planner_error + - logical_plan +- name: with normal column + sql: WITH RECURSIVE t(a) AS (VALUES(1) UNION ALL SELECT a + 1 FROM t WHERE a < 100) SELECT * FROM t; + expected_outputs: + - logical_plan - name: name a is leaked outside sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a; expected_outputs: - - binder_error + - binder_error \ No newline at end of file diff --git a/src/frontend/planner_test/tests/testdata/input/shared_source.yml b/src/frontend/planner_test/tests/testdata/input/shared_source.yml index 0f68cc25f6288..952ae8dcc5aa0 100644 --- a/src/frontend/planner_test/tests/testdata/input/shared_source.yml +++ b/src/frontend/planner_test/tests/testdata/input/shared_source.yml @@ -25,7 +25,7 @@ # We use with_config_map to control the config when CREATE SOURCE, and use another SET statement to change the config for CREATE MV # # batch: All 4 plans should be the same. -# stream: StreamSourceScan (with backfill) should be used only for the last 1. All other 3 use StreamSource. +# stream: StreamSourceScan (with backfill) should be used only for the last 2. The first 2 use StreamSource. rw_enable_shared_source changes the behavior of CREATE SOURCE, but not CREATE MATERIALIZED VIEW - with_config_map: rw_enable_shared_source: false before: diff --git a/src/frontend/planner_test/tests/testdata/input/short_circuit.yaml b/src/frontend/planner_test/tests/testdata/input/short_circuit.yaml index 08b4b08471116..83e518e395b51 100644 --- a/src/frontend/planner_test/tests/testdata/input/short_circuit.yaml +++ b/src/frontend/planner_test/tests/testdata/input/short_circuit.yaml @@ -40,6 +40,12 @@ expected_outputs: - logical_plan - batch_plan +- id : short_circuit_optimize_time + sql: | + select true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true; + expected_outputs: + - logical_plan + - batch_plan # should *not* be identified as const # otherwise the *semantic* will be inconsistent diff --git a/src/frontend/planner_test/tests/testdata/input/sink_into_table.yaml b/src/frontend/planner_test/tests/testdata/input/sink_into_table.yaml new file mode 100644 index 0000000000000..1191cd6a68966 --- /dev/null +++ b/src/frontend/planner_test/tests/testdata/input/sink_into_table.yaml @@ -0,0 +1,11 @@ +- id: create sink into table having default expression with now() + sql: | + create table t( + x int, + y timestamptz default now(), + z timestamptz default now() - interval '1 minute' + ) append only; + create table s(x int) append only; + explain create sink ss into t from s with (type = 'append-only'); + expected_outputs: + - explain_output diff --git a/src/frontend/planner_test/tests/testdata/input/subquery_expr_correlated.yaml b/src/frontend/planner_test/tests/testdata/input/subquery_expr_correlated.yaml index 5ba9930be99b9..f35703128ae7d 100644 --- a/src/frontend/planner_test/tests/testdata/input/subquery_expr_correlated.yaml +++ b/src/frontend/planner_test/tests/testdata/input/subquery_expr_correlated.yaml @@ -508,3 +508,49 @@ expected_outputs: - batch_plan - stream_plan +- name: improve multi scalar subqueries optimization time. issue 16952. case 1. + sql: | + create table t1(a int, b int); + create table t2(c int primary key, d int); + select + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col1, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col2, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col3, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col4, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col5, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col6, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col7, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col8, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col9, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col10, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col11, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col12, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col13, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col14, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col15, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col16, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col17, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col18 + from t1; + expected_outputs: + - batch_plan + - stream_plan +- name: improve multi scalar subqueries optimization time. issue 16952. case 2. + sql: | + create table t1(a int, b int); + create table t2(c int primary key, d int); + create table t3(e int, f int); + create table t4(g int, h int); + create table t5(i int, j int); + create table t6(k int, l int); + select + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col1, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col2, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col3, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col4, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col5, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col6 + from t1; + expected_outputs: + - batch_plan + - stream_plan diff --git a/src/frontend/planner_test/tests/testdata/output/agg.yaml b/src/frontend/planner_test/tests/testdata/output/agg.yaml index e618a58500783..4c75b83187741 100644 --- a/src/frontend/planner_test/tests/testdata/output/agg.yaml +++ b/src/frontend/planner_test/tests/testdata/output/agg.yaml @@ -451,6 +451,12 @@ LogicalAgg { group_key: [t.v1], aggs: [] } └─LogicalProject { exprs: [t.v1] } └─LogicalScan { table: t, columns: [t.v1, t.v2, t._row_id] } + stream_plan: |- + StreamMaterialize { columns: [v1], stream_key: [v1], pk_columns: [v1], pk_conflict: NoCheck } + └─StreamProject { exprs: [t.v1], noop_update_hint: true } + └─StreamHashAgg { group_key: [t.v1], aggs: [count] } + └─StreamExchange { dist: HashShard(t.v1) } + └─StreamTableScan { table: t, columns: [t.v1, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - name: distinct with agg sql: | create table t (v1 int, v2 int); @@ -461,6 +467,15 @@ └─LogicalAgg { group_key: [t.v2], aggs: [sum(t.v1)] } └─LogicalProject { exprs: [t.v2, t.v1] } └─LogicalScan { table: t, columns: [t.v1, t.v2, t._row_id] } + stream_plan: |- + StreamMaterialize { columns: [sum], stream_key: [sum], pk_columns: [sum], pk_conflict: NoCheck } + └─StreamProject { exprs: [sum(t.v1)], noop_update_hint: true } + └─StreamHashAgg { group_key: [sum(t.v1)], aggs: [count] } + └─StreamExchange { dist: HashShard(sum(t.v1)) } + └─StreamProject { exprs: [t.v2, sum(t.v1)] } + └─StreamHashAgg { group_key: [t.v2], aggs: [sum(t.v1), count] } + └─StreamExchange { dist: HashShard(t.v2) } + └─StreamTableScan { table: t, columns: [t.v1, t.v2, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - name: distinct on sql: | create table t (v1 int, v2 int, v3 int); @@ -576,7 +591,7 @@ └─LogicalScan { table: t, columns: [t.v1] } stream_plan: |- StreamMaterialize { columns: [v1], stream_key: [v1], pk_columns: [v1], pk_conflict: NoCheck } - └─StreamProject { exprs: [t.v1] } + └─StreamProject { exprs: [t.v1], noop_update_hint: true } └─StreamHashAgg { group_key: [t.v1], aggs: [count] } └─StreamExchange { dist: HashShard(t.v1) } └─StreamTableScan { table: t, columns: [t.v1, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -1278,7 +1293,7 @@ logical_plan: |- LogicalProject { exprs: [Case((count(t.v1) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - ((sum(t.v1)::Decimal * sum(t.v1)::Decimal) / count(t.v1)::Decimal)) / (count(t.v1) - 1:Int64)::Decimal))) as $expr2, Sqrt(((sum($expr1)::Decimal - ((sum(t.v1)::Decimal * sum(t.v1)::Decimal) / count(t.v1)::Decimal)) / count(t.v1)::Decimal)) as $expr3] } └─LogicalAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } - └─LogicalProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1] } + └─LogicalProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1] } └─LogicalScan { table: t, columns: [t.v1, t._row_id] } batch_plan: |- BatchProject { exprs: [Case((sum0(count(t.v1)) <= 1:Int64), null:Decimal, Sqrt(((sum(sum($expr1))::Decimal - (($expr2 * $expr2) / $expr3)) / (sum0(count(t.v1)) - 1:Int64)::Decimal))) as $expr4, Sqrt(((sum(sum($expr1))::Decimal - (($expr2 * $expr2) / $expr3)) / $expr3)) as $expr5] } @@ -1286,14 +1301,14 @@ └─BatchSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v1)), sum0(count(t.v1))] } └─BatchExchange { order: [], dist: Single } └─BatchSimpleAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } - └─BatchProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1] } + └─BatchProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1] } └─BatchScan { table: t, columns: [t.v1], distribution: SomeShard } batch_local_plan: |- BatchProject { exprs: [Case((count(t.v1) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - (($expr2 * $expr2) / $expr3)) / (count(t.v1) - 1:Int64)::Decimal))) as $expr4, Sqrt(((sum($expr1)::Decimal - (($expr2 * $expr2) / $expr3)) / $expr3)) as $expr5] } └─BatchProject { exprs: [sum($expr1), sum(t.v1), count(t.v1), sum(t.v1)::Decimal as $expr2, count(t.v1)::Decimal as $expr3] } └─BatchSimpleAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } └─BatchExchange { order: [], dist: Single } - └─BatchProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1] } + └─BatchProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1] } └─BatchScan { table: t, columns: [t.v1], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [stddev_samp, stddev_pop], stream_key: [], pk_columns: [], pk_conflict: NoCheck } @@ -1302,7 +1317,7 @@ └─StreamSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v1)), sum0(count(t.v1)), count] } └─StreamExchange { dist: Single } └─StreamStatelessSimpleAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } - └─StreamProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1, t._row_id] } + └─StreamProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1, t._row_id] } └─StreamTableScan { table: t, columns: [t.v1, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - name: stddev_samp with other columns sql: | @@ -1310,7 +1325,7 @@ logical_plan: |- LogicalProject { exprs: [count('':Varchar), Case((count(1:Int32) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - ((sum(1:Int32)::Decimal * sum(1:Int32)::Decimal) / count(1:Int32)::Decimal)) / (count(1:Int32) - 1:Int64)::Decimal))) as $expr2] } └─LogicalAgg { aggs: [count('':Varchar), sum($expr1), sum(1:Int32), count(1:Int32)] } - └─LogicalProject { exprs: ['':Varchar, 1:Int32, (1:Int32 * 1:Int32) as $expr1] } + └─LogicalProject { exprs: ['':Varchar, (1:Int32 * 1:Int32) as $expr1, 1:Int32] } └─LogicalValues { rows: [[]], schema: Schema { fields: [] } } - name: stddev_samp with group sql: | @@ -1319,7 +1334,7 @@ logical_plan: |- LogicalProject { exprs: [Case((count(t.v) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - ((sum(t.v)::Decimal * sum(t.v)::Decimal) / count(t.v)::Decimal)) / (count(t.v) - 1:Int64)::Decimal))) as $expr2] } └─LogicalAgg { group_key: [t.w], aggs: [sum($expr1), sum(t.v), count(t.v)] } - └─LogicalProject { exprs: [t.w, t.v, (t.v * t.v) as $expr1] } + └─LogicalProject { exprs: [t.w, (t.v * t.v) as $expr1, t.v] } └─LogicalScan { table: t, columns: [t.v, t.w, t._row_id] } - name: force two phase aggregation should succeed with UpstreamHashShard and SomeShard (batch only). sql: | @@ -1797,7 +1812,7 @@ stream_plan: |- StreamMaterialize { columns: [a, row_number], stream_key: [a], pk_columns: [a], pk_conflict: NoCheck } └─StreamOverWindow { window_functions: [row_number() OVER(PARTITION BY t.a ORDER BY t.a DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)] } - └─StreamProject { exprs: [t.a] } + └─StreamProject { exprs: [t.a], noop_update_hint: true } └─StreamHashAgg { group_key: [t.a], aggs: [count] } └─StreamExchange { dist: HashShard(t.a) } └─StreamTableScan { table: t, columns: [t.a, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } diff --git a/src/frontend/planner_test/tests/testdata/output/create_source.yaml b/src/frontend/planner_test/tests/testdata/output/create_source.yaml index 9106ce7af8a72..887b068cc9638 100644 --- a/src/frontend/planner_test/tests/testdata/output/create_source.yaml +++ b/src/frontend/planner_test/tests/testdata/output/create_source.yaml @@ -11,7 +11,7 @@ sql: | create source s with(connector='kafka') FORMAT PLAIN ENCODE JSON; planner_error: 'Protocol error: Schema definition is required, either from SQL or schema registry.' -- id: csv_delimiter_tab +- id: csv_delimiter_comma sql: | explain create table s0 (v1 int, v2 varchar) with ( connector = 'kafka', @@ -28,6 +28,23 @@ └─StreamExchange { dist: HashShard(_row_id) } └─StreamDml { columns: [v1, v2, _row_id] } └─StreamSource +- id: csv_delimiter_semicolon + sql: | + explain create table s0 (v1 int, v2 varchar) with ( + connector = 'kafka', + topic = 'kafka_1_csv_topic', + properties.bootstrap.server = 'message_queue:29092', + scan.startup.mode = 'earliest' + ) FORMAT PLAIN ENCODE CSV (delimiter = ';', without_header = true); + explain_output: | + StreamMaterialize { columns: [v1, v2, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: Overwrite } + └─StreamRowIdGen { row_id_index: 2 } + └─StreamUnion { all: true } + ├─StreamExchange [no_shuffle] { dist: SomeShard } + │ └─StreamSource { source: s0, columns: [v1, v2, _row_id] } + └─StreamExchange { dist: HashShard(_row_id) } + └─StreamDml { columns: [v1, v2, _row_id] } + └─StreamSource - id: csv_delimiter_tab sql: | explain create table s0 (v1 int, v2 varchar) with ( diff --git a/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml b/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml index ceb706446f986..0e5d72b3499a3 100644 --- a/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml +++ b/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml @@ -67,7 +67,7 @@ └─BatchSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v)), sum0(count(t.v))] } └─BatchExchange { order: [], dist: Single } └─BatchSimpleAgg { aggs: [sum($expr1), sum(t.v), count(t.v)] } - └─BatchProject { exprs: [t.v, (t.v * t.v) as $expr1] } + └─BatchProject { exprs: [(t.v * t.v) as $expr1, t.v] } └─BatchScan { table: t, columns: [t.v], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [stddev_pop, stddev_samp, var_pop, var_samp], stream_key: [], pk_columns: [], pk_conflict: NoCheck } @@ -78,7 +78,7 @@ └─StreamSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v)), sum0(count(t.v)), count] } └─StreamExchange { dist: Single } └─StreamStatelessSimpleAgg { aggs: [sum($expr1), sum(t.v), count(t.v)] } - └─StreamProject { exprs: [t.v, (t.v * t.v) as $expr1, t._row_id] } + └─StreamProject { exprs: [(t.v * t.v) as $expr1, t.v, t._row_id] } └─StreamTableScan { table: t, columns: [t.v, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - name: Common sub expression shouldn't extract partial expression of `some`/`all`. See 11766 sql: | diff --git a/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml b/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml index f819990200576..89aea24f2bd80 100644 --- a/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml +++ b/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml @@ -40,7 +40,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: With Simple Agg upstream materialized view on inner side before: - create_tables @@ -97,7 +97,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: Ensure error on output columns from inner before: - create_tables @@ -111,7 +111,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: Use Inner Join for equi condition before: - create_tables diff --git a/src/frontend/planner_test/tests/testdata/output/except.yaml b/src/frontend/planner_test/tests/testdata/output/except.yaml index 58b4f757955c3..d19ec9882c42e 100644 --- a/src/frontend/planner_test/tests/testdata/output/except.yaml +++ b/src/frontend/planner_test/tests/testdata/output/except.yaml @@ -25,7 +25,7 @@ └─BatchScan { table: t2, columns: [t2.a, t2.b, t2.c], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } - └─StreamProject { exprs: [t1.a, t1.b, t1.c] } + └─StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └─StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } └─StreamHashJoin { type: LeftAnti, predicate: t1.a IS NOT DISTINCT FROM t2.a AND t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t2.c, output: all } ├─StreamExchange { dist: HashShard(t1.a, t1.b, t1.c) } @@ -35,7 +35,7 @@ stream_dist_plan: |+ Fragment 0 StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } { tables: [ Materialize: 4294967294 ] } - └── StreamProject { exprs: [t1.a, t1.b, t1.c] } + └── StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └── StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } { tables: [ HashAggState: 0 ] } └── StreamHashJoin { type: LeftAnti, predicate: t1.a IS NOT DISTINCT FROM t2.a AND t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t2.c, output: all } ├── tables: [ HashJoinLeft: 1, HashJoinDegreeLeft: 2, HashJoinRight: 3, HashJoinDegreeRight: 4 ] diff --git a/src/frontend/planner_test/tests/testdata/output/expr.yaml b/src/frontend/planner_test/tests/testdata/output/expr.yaml index 924547196357e..f88f7c4d69b76 100644 --- a/src/frontend/planner_test/tests/testdata/output/expr.yaml +++ b/src/frontend/planner_test/tests/testdata/output/expr.yaml @@ -88,7 +88,7 @@ create table t (v1 int); SELECT 1 in (3, 0.5*2, min(v1)) from t; batch_plan: |- - BatchProject { exprs: [true:Boolean] } + BatchProject { exprs: [(true:Boolean OR (1:Int32 = min(min(t.v1)))) as $expr1] } └─BatchSimpleAgg { aggs: [min(min(t.v1))] } └─BatchExchange { order: [], dist: Single } └─BatchSimpleAgg { aggs: [min(t.v1)] } @@ -543,7 +543,7 @@ Failed to bind expression: v1 >= now() Caused by: - Invalid input syntax: For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING` and `ON`. Found in clause: Some(GroupBy). Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information + Invalid input syntax: For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING`, `ON` and `FROM`. Found in clause: Some(GroupBy). Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information - name: forbid now in select for stream sql: | create table t (v1 timestamp with time zone, v2 timestamp with time zone); @@ -552,7 +552,7 @@ Failed to bind expression: now() Caused by: - Invalid input syntax: For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING` and `ON`. Found in clause: None. Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information + Invalid input syntax: For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING`, `ON` and `FROM`. Found in clause: None. Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information - name: forbid now in agg filter for stream sql: | create table t (v1 timestamp with time zone, v2 int); @@ -561,7 +561,7 @@ Failed to bind expression: sum(v2) FILTER(WHERE v1 >= now()) Caused by: - Invalid input syntax: For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING` and `ON`. Found in clause: Some(Filter). Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information + Invalid input syntax: For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING`, `ON` and `FROM`. Found in clause: Some(Filter). Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information - name: typo pg_teminate_backend sql: | select pg_teminate_backend(1); @@ -593,8 +593,9 @@ batch_error: | Expr error - Caused by: - Division by zero + Caused by these errors (recent errors listed first): + 1: error while evaluating expression `general_div('1', '0')` + 2: Division by zero - sql: | select * from abs(-1); batch_plan: 'BatchValues { rows: [[1:Int32]] }' @@ -615,6 +616,23 @@ - sql: | select * from max(); binder_error: 'Invalid input syntax: aggregate functions are not allowed in FROM' +- sql: | + create table t (k int, j jsonb); + select a * k + b from t, jsonb_populate_record(null::struct, j); + batch_plan: |- + BatchExchange { order: [], dist: Single } + └─BatchProject { exprs: [((Field($expr1, 0:Int32) * t.k) + Field($expr1, 1:Int32)) as $expr2] } + └─BatchHashJoin { type: Inner, predicate: t.j IS NOT DISTINCT FROM t.j, output: all } + ├─BatchExchange { order: [], dist: HashShard(t.j) } + │ └─BatchScan { table: t, columns: [t.k, t.j], distribution: SomeShard } + └─BatchExchange { order: [], dist: HashShard(t.j) } + └─BatchProject { exprs: [t.j, JsonbPopulateRecord(null:Struct(StructType { field_names: ["a", "b"], field_types: [Int32, Int32] }), t.j) as $expr1] } + └─BatchNestedLoopJoin { type: Inner, predicate: true, output: all } + ├─BatchExchange { order: [], dist: Single } + │ └─BatchHashAgg { group_key: [t.j], aggs: [] } + │ └─BatchExchange { order: [], dist: HashShard(t.j) } + │ └─BatchScan { table: t, columns: [t.j], distribution: SomeShard } + └─BatchValues { rows: [[]] } - name: Grafana issue-10134 sql: | SELECT * FROM @@ -631,7 +649,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: without variadic keyword sql: | create table t(a varchar, b varchar); diff --git a/src/frontend/planner_test/tests/testdata/output/format.yaml b/src/frontend/planner_test/tests/testdata/output/format.yaml index 89b32834c381e..513e377911caf 100644 --- a/src/frontend/planner_test/tests/testdata/output/format.yaml +++ b/src/frontend/planner_test/tests/testdata/output/format.yaml @@ -21,8 +21,9 @@ batch_error: | Expr error - Caused by: - too few arguments for format() + Caused by these errors (recent errors listed first): + 1: error while evaluating expression `format('one', 'two')` + 2: too few arguments for format() - sql: | SELECT format('Testing %s, %s, %s, %', 'one', 'two', 'three'); batch_error: | diff --git a/src/frontend/planner_test/tests/testdata/output/generate_series_with_now.yaml b/src/frontend/planner_test/tests/testdata/output/generate_series_with_now.yaml new file mode 100644 index 0000000000000..5941f7a281ddb --- /dev/null +++ b/src/frontend/planner_test/tests/testdata/output/generate_series_with_now.yaml @@ -0,0 +1,68 @@ +# This file is automatically generated. See `src/frontend/planner_test/README.md` for more information. +- sql: | + select * from generate_series( + '2024-06-21 17:36:00'::timestamptz, + now(), + interval '1 hour' + ); + logical_plan: |- + LogicalProject { exprs: [generate_series] } + └─LogicalTableFunction { table_function: GenerateSeries('2024-06-21 17:36:00':Varchar::Timestamptz, Now, '01:00:00':Interval) } + optimized_logical_plan_for_stream: 'LogicalNow { output: [ts] }' + stream_plan: |- + StreamMaterialize { columns: [generate_series], stream_key: [generate_series], pk_columns: [generate_series], pk_conflict: NoCheck, watermark_columns: [generate_series] } + └─StreamNow { output: [ts] } +- sql: | + select * from generate_series( + '2024-06-21 17:36:00'::timestamp, -- `timestamp` type is not supported + now(), + interval '1 hour' + ); + binder_error: function generate_series(timestamp without time zone, timestamp with time zone, interval) does not exist +- sql: | + select * from generate_series( + now() - interval '1 hour', + now(), + interval '1 hour' + ); + stream_error: |- + Not supported: General `now()` function in streaming queries + HINT: Streaming `now()` is currently only supported in GenerateSeries and TemporalFilter patterns. +- sql: | + select * from unnest(array[now(), now()]); + stream_error: |- + Not supported: General `now()` function in streaming queries + HINT: Streaming `now()` is currently only supported in GenerateSeries and TemporalFilter patterns. +- sql: | + select 1::int as constant from generate_series( + '2024-06-21 17:36:00'::timestamptz, + now(), + interval '1 hour' + ); + logical_plan: |- + LogicalProject { exprs: [1:Int32] } + └─LogicalTableFunction { table_function: GenerateSeries('2024-06-21 17:36:00':Varchar::Timestamptz, Now, '01:00:00':Interval) } + optimized_logical_plan_for_stream: 'LogicalValues { rows: [], schema: Schema { fields: [1:Int32:Int32] } }' + stream_plan: |- + StreamMaterialize { columns: [constant, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } + └─StreamValues { rows: [] } +- sql: | + select + extract('year' from t) as year, + extract('month' from t) as month, + 1::int as constant + from generate_series( + '2024-01-01 00:00:00+00'::timestamptz, + now(), + interval '1 month' + ) as s(t); + logical_plan: |- + LogicalProject { exprs: [Extract('YEAR':Varchar, generate_series) as $expr1, Extract('MONTH':Varchar, generate_series) as $expr2, 1:Int32] } + └─LogicalTableFunction { table_function: GenerateSeries('2024-01-01 00:00:00+00:00':Timestamptz, Now, '1 mon':Interval) } + optimized_logical_plan_for_stream: |- + LogicalProject { exprs: [Extract('YEAR':Varchar, ts) as $expr1, Extract('MONTH':Varchar, ts) as $expr2, 1:Int32] } + └─LogicalNow { output: [ts] } + stream_plan: |- + StreamMaterialize { columns: [year, month, constant, ts(hidden)], stream_key: [ts], pk_columns: [ts], pk_conflict: NoCheck, watermark_columns: [ts(hidden)] } + └─StreamProject { exprs: [Extract('YEAR':Varchar, ts, 'UTC':Varchar) as $expr1, Extract('MONTH':Varchar, ts, 'UTC':Varchar) as $expr2, 1:Int32, ts], output_watermarks: [ts] } + └─StreamNow { output: [ts] } diff --git a/src/frontend/planner_test/tests/testdata/output/index_selection.yaml b/src/frontend/planner_test/tests/testdata/output/index_selection.yaml index 7840e6b48e5f8..67a89e0878f9b 100644 --- a/src/frontend/planner_test/tests/testdata/output/index_selection.yaml +++ b/src/frontend/planner_test/tests/testdata/output/index_selection.yaml @@ -332,7 +332,7 @@ select * from t1 where a = 1 or b = 2 or c = 3 or p = 4 or a = 5 batch_plan: |- BatchExchange { order: [], dist: Single } - └─BatchLookupJoin { type: Inner, predicate: idx1.t1._row_id IS NOT DISTINCT FROM t1._row_id AND (((((t1.a = 1:Int32) OR (t1.b = 2:Decimal)) OR (t1.c = 3:Int32)) OR (t1.p = 4:Int32)) OR (t1.a = 5:Int32)), output: [t1.a, t1.b, t1.c, t1.p], lookup table: t1 } + └─BatchLookupJoin { type: Inner, predicate: idx1.t1._row_id IS NOT DISTINCT FROM t1._row_id AND ((((t1.a = 1:Int32) OR (t1.b = 2:Decimal)) OR ((t1.c = 3:Int32) OR (t1.p = 4:Int32))) OR (t1.a = 5:Int32)), output: [t1.a, t1.b, t1.c, t1.p], lookup table: t1 } └─BatchExchange { order: [], dist: UpstreamHashShard(idx1.t1._row_id) } └─BatchHashAgg { group_key: [idx1.t1._row_id], aggs: [] } └─BatchExchange { order: [], dist: HashShard(idx1.t1._row_id) } @@ -346,7 +346,7 @@ └─BatchExchange { order: [], dist: Single } └─BatchScan { table: idx4, columns: [idx4.t1._row_id], scan_ranges: [idx4.p = Int32(4)], distribution: SomeShard } batch_local_plan: |- - BatchLookupJoin { type: Inner, predicate: idx1.t1._row_id IS NOT DISTINCT FROM t1._row_id AND (((((t1.a = 1:Int32) OR (t1.b = 2:Decimal)) OR (t1.c = 3:Int32)) OR (t1.p = 4:Int32)) OR (t1.a = 5:Int32)), output: [t1.a, t1.b, t1.c, t1.p], lookup table: t1 } + BatchLookupJoin { type: Inner, predicate: idx1.t1._row_id IS NOT DISTINCT FROM t1._row_id AND ((((t1.a = 1:Int32) OR (t1.b = 2:Decimal)) OR ((t1.c = 3:Int32) OR (t1.p = 4:Int32))) OR (t1.a = 5:Int32)), output: [t1.a, t1.b, t1.c, t1.p], lookup table: t1 } └─BatchHashAgg { group_key: [idx1.t1._row_id], aggs: [] } └─BatchUnion { all: true } ├─BatchExchange { order: [], dist: Single } @@ -766,3 +766,12 @@ BatchExchange { order: [], dist: Single } └─BatchProject { exprs: [AtTimeZone(i.ts, 'UTC':Varchar) as $expr1] } └─BatchScan { table: i, columns: [i.ts], scan_ranges: [i.AT_TIME_ZONE > Timestamptz(Timestamptz(1617235200000000))], distribution: SomeShard } +- name: funtional index with timezone2 + sql: | + create table t (a int, ts timestamp without time zone); + create index idx on t (CAST(ts AS timestamptz)); + select * from t order by ts; + batch_plan: |- + BatchExchange { order: [t.ts ASC], dist: Single } + └─BatchSort { order: [t.ts ASC] } + └─BatchScan { table: t, columns: [t.a, t.ts], distribution: SomeShard } diff --git a/src/frontend/planner_test/tests/testdata/output/intersect.yaml b/src/frontend/planner_test/tests/testdata/output/intersect.yaml index 87834b2ca2d2e..678091bbb2e47 100644 --- a/src/frontend/planner_test/tests/testdata/output/intersect.yaml +++ b/src/frontend/planner_test/tests/testdata/output/intersect.yaml @@ -25,7 +25,7 @@ └─BatchScan { table: t2, columns: [t2.a, t2.b, t2.c], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } - └─StreamProject { exprs: [t1.a, t1.b, t1.c] } + └─StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └─StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } └─StreamHashJoin { type: LeftSemi, predicate: t1.a IS NOT DISTINCT FROM t2.a AND t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t2.c, output: all } ├─StreamExchange { dist: HashShard(t1.a, t1.b, t1.c) } @@ -35,7 +35,7 @@ stream_dist_plan: |+ Fragment 0 StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } { tables: [ Materialize: 4294967294 ] } - └── StreamProject { exprs: [t1.a, t1.b, t1.c] } + └── StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └── StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } { tables: [ HashAggState: 0 ] } └── StreamHashJoin { type: LeftSemi, predicate: t1.a IS NOT DISTINCT FROM t2.a AND t1.b IS NOT DISTINCT FROM t2.b AND t1.c IS NOT DISTINCT FROM t2.c, output: all } ├── tables: [ HashJoinLeft: 1, HashJoinDegreeLeft: 2, HashJoinRight: 3, HashJoinDegreeRight: 4 ] diff --git a/src/frontend/planner_test/tests/testdata/output/join.yaml b/src/frontend/planner_test/tests/testdata/output/join.yaml index 2db3b8cc3994a..a1617d04e7d2e 100644 --- a/src/frontend/planner_test/tests/testdata/output/join.yaml +++ b/src/frontend/planner_test/tests/testdata/output/join.yaml @@ -204,7 +204,7 @@ StreamMaterialize { columns: [x, i.t._row_id(hidden), i.t._row_id#1(hidden), i.x(hidden), i.t._row_id#2(hidden), i.t._row_id#3(hidden), i.x#1(hidden)], stream_key: [i.t._row_id, i.t._row_id#1, i.x, i.t._row_id#2, i.t._row_id#3, i.x#1], pk_columns: [i.t._row_id, i.t._row_id#1, i.x, i.t._row_id#2, i.t._row_id#3, i.x#1], pk_conflict: NoCheck } └─StreamExchange { dist: HashShard(i.t._row_id, i.t._row_id, i.x, i.t._row_id, i.t._row_id, i.x) } └─StreamProject { exprs: [Coalesce(i.x, i.x) as $expr1, i.t._row_id, i.t._row_id, i.x, i.t._row_id, i.t._row_id, i.x] } - └─StreamFilter { predicate: (((((IsNotNull(i.t._row_id) OR IsNotNull(i.t._row_id)) OR IsNotNull(i.x)) OR IsNotNull(i.t._row_id)) OR IsNotNull(i.t._row_id)) OR IsNotNull(i.x)) } + └─StreamFilter { predicate: (((IsNotNull(i.t._row_id) OR IsNotNull(i.t._row_id)) OR (IsNotNull(i.x) OR IsNotNull(i.t._row_id))) OR (IsNotNull(i.t._row_id) OR IsNotNull(i.x))) } └─StreamHashJoin { type: FullOuter, predicate: i.x = i.x, output: [i.x, i.x, i.t._row_id, i.t._row_id, i.t._row_id, i.t._row_id] } ├─StreamShare { id: 4 } │ └─StreamHashJoin { type: Inner, predicate: i.x = i.x, output: [i.x, i.t._row_id, i.t._row_id] } diff --git a/src/frontend/planner_test/tests/testdata/output/lateral_subquery.yaml b/src/frontend/planner_test/tests/testdata/output/lateral_subquery.yaml index acac3cc236994..815890d6a73b8 100644 --- a/src/frontend/planner_test/tests/testdata/output/lateral_subquery.yaml +++ b/src/frontend/planner_test/tests/testdata/output/lateral_subquery.yaml @@ -164,7 +164,7 @@ ├─StreamExchange { dist: HashShard(t.arr) } │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } + └─StreamProject { exprs: [t.arr], noop_update_hint: true } └─StreamHashAgg { group_key: [t.arr], aggs: [count] } └─StreamExchange { dist: HashShard(t.arr) } └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -193,7 +193,7 @@ │ │ └─StreamTableScan { table: t1, columns: [t1.n, t1.id, t1._row_id, t1.c], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } │ └─StreamExchange { dist: HashShard(Unnest(Case(($1 <> '':Varchar), ArrayAppend(StringToArray(Trim($1, ',':Varchar), ',':Varchar), $3), Array($3)))) } │ └─StreamProjectSet { select_list: [$0, $1, $2, $3, Unnest(Case(($1 <> '':Varchar), ArrayAppend(StringToArray(Trim($1, ',':Varchar), ',':Varchar), $3), Array($3)))] } - │ └─StreamProject { exprs: [t2.p, t2.p, t2.d, t2.d] } + │ └─StreamProject { exprs: [t2.p, t2.p, t2.d, t2.d], noop_update_hint: true } │ └─StreamHashAgg { group_key: [t2.p, t2.p, t2.d, t2.d], aggs: [count] } │ └─StreamExchange { dist: HashShard(t2.p, t2.p, t2.d, t2.d) } │ └─StreamProject { exprs: [t2.p, t2.p, t2.d, t2.d, t2._row_id] } @@ -226,7 +226,7 @@ └─StreamHashAgg { group_key: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5], aggs: [sum($expr4) filter(((t2.c7 * t1.c3) <= (t2.c7 * t2.c8))), sum(t1.c2) filter(((t2.c7 * t1.c3) <= (t2.c7 * t2.c8))), count] } └─StreamHashJoin { type: LeftOuter, predicate: t2.c7 IS NOT DISTINCT FROM t2.c7 AND t2.c7 IS NOT DISTINCT FROM t2.c7 AND t2.c8 IS NOT DISTINCT FROM t2.c8 AND t2.c7 IS NOT DISTINCT FROM t2.c7 AND t2.c7 IS NOT DISTINCT FROM t2.c7 AND t2.c8 IS NOT DISTINCT FROM t2.c8 AND t2.c1 IS NOT DISTINCT FROM t2.c1 AND t2.c5 IS NOT DISTINCT FROM t2.c5 AND t2.c5 IS NOT DISTINCT FROM t2.c5, output: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5, t1.c3, $expr4, t1.c2, t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5, t1._row_id, $expr1] } ├─StreamExchange { dist: HashShard(t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5) } - │ └─StreamProject { exprs: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5] } + │ └─StreamProject { exprs: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5], noop_update_hint: true } │ └─StreamHashAgg { group_key: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5], aggs: [count] } │ └─StreamExchange { dist: HashShard(t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5) } │ └─StreamTableScan { table: t2, columns: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5, t2._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t2._row_id], pk: [_row_id], dist: UpstreamHashShard(t2._row_id) } @@ -235,10 +235,38 @@ └─StreamFilter { predicate: (t2.c5 <= $expr2) } └─StreamHashJoin { type: Inner, predicate: t2.c1 = t1.c1 AND $expr1 = $expr3, output: all } ├─StreamExchange { dist: HashShard(t2.c1, $expr1) } - │ └─StreamProject { exprs: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5, AtTimeZone(t2.c5, 'UTC':Varchar)::Date as $expr1] } + │ └─StreamProject { exprs: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5, AtTimeZone(t2.c5, 'UTC':Varchar)::Date as $expr1], noop_update_hint: true } │ └─StreamHashAgg { group_key: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5], aggs: [count] } │ └─StreamExchange { dist: HashShard(t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5) } │ └─StreamTableScan { table: t2, columns: [t2.c7, t2.c7, t2.c8, t2.c7, t2.c7, t2.c8, t2.c1, t2.c5, t2.c5, t2._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t2._row_id], pk: [_row_id], dist: UpstreamHashShard(t2._row_id) } └─StreamExchange { dist: HashShard(t1.c1, $expr3) } └─StreamProject { exprs: [t1.c1, t1.c2, t1.c3, AtTimeZone(t1.c4, 'UTC':Varchar) as $expr2, t1.c4::Date as $expr3, t1._row_id] } └─StreamTableScan { table: t1, columns: [t1.c1, t1.c2, t1.c3, t1.c4, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } +- name: https://github.com/risingwavelabs/risingwave/issues/17382 + sql: | + CREATE TABLE r(ts TIMESTAMPTZ, src_id int, dev_id int); + SELECT e.ts AS e_ts, d.* + FROM ( + SELECT '2024-06-20T19:01:00Z'::TIMESTAMPTZ ts, 1::INT AS src_id) e + JOIN LATERAL + ( + SELECT DISTINCT ON(src_id, dev_id) * + FROM r + WHERE r.src_id = e.src_id AND r.ts <= e.ts + ORDER BY src_id, dev_id, ts DESC + )d on true; + batch_plan: |- + BatchExchange { order: [], dist: Single } + └─BatchHashJoin { type: Inner, predicate: 1:Int32 IS NOT DISTINCT FROM 1:Int32 AND '2024-06-20 19:01:00+00:00':Timestamptz IS NOT DISTINCT FROM '2024-06-20 19:01:00+00:00':Timestamptz, output: ['2024-06-20 19:01:00+00:00':Timestamptz, r.ts, r.src_id, r.dev_id] } + ├─BatchExchange { order: [], dist: HashShard(1:Int32, '2024-06-20 19:01:00+00:00':Timestamptz) } + │ └─BatchValues { rows: [['2024-06-20 19:01:00+00:00':Timestamptz, 1:Int32]] } + └─BatchExchange { order: [], dist: HashShard(1:Int32, '2024-06-20 19:01:00+00:00':Timestamptz) } + └─BatchGroupTopN { order: [r.src_id ASC, r.dev_id ASC, r.ts DESC], limit: 1, offset: 0, group_key: [1:Int32, '2024-06-20 19:01:00+00:00':Timestamptz, r.src_id, r.dev_id] } + └─BatchExchange { order: [], dist: HashShard(1:Int32, '2024-06-20 19:01:00+00:00':Timestamptz, r.src_id, r.dev_id) } + └─BatchHashJoin { type: Inner, predicate: 1:Int32 = r.src_id AND (r.ts <= '2024-06-20 19:01:00+00:00':Timestamptz), output: all } + ├─BatchExchange { order: [], dist: HashShard(1:Int32) } + │ └─BatchHashAgg { group_key: [1:Int32, '2024-06-20 19:01:00+00:00':Timestamptz], aggs: [] } + │ └─BatchExchange { order: [], dist: HashShard(1:Int32, '2024-06-20 19:01:00+00:00':Timestamptz) } + │ └─BatchValues { rows: [[1:Int32, '2024-06-20 19:01:00+00:00':Timestamptz]] } + └─BatchExchange { order: [], dist: HashShard(r.src_id) } + └─BatchScan { table: r, columns: [r.ts, r.src_id, r.dev_id], distribution: SomeShard } diff --git a/src/frontend/planner_test/tests/testdata/output/nexmark.yaml b/src/frontend/planner_test/tests/testdata/output/nexmark.yaml index dcdd34ed2c153..d6b90da0a8c1a 100644 --- a/src/frontend/planner_test/tests/testdata/output/nexmark.yaml +++ b/src/frontend/planner_test/tests/testdata/output/nexmark.yaml @@ -50,8 +50,7 @@ └─BatchScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time], distribution: SomeShard } sink_plan: |- StreamSink { type: append-only, columns: [auction, bidder, price, date_time, bid._row_id(hidden)] } - └─StreamExchange { dist: Single } - └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } + └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } stream_plan: |- StreamMaterialize { columns: [auction, bidder, price, date_time, bid._row_id(hidden)], stream_key: [bid._row_id], pk_columns: [bid._row_id], pk_conflict: NoCheck } └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } @@ -83,9 +82,8 @@ └─BatchScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time], distribution: SomeShard } sink_plan: |- StreamSink { type: append-only, columns: [auction, bidder, price, date_time, bid._row_id(hidden)] } - └─StreamExchange { dist: Single } - └─StreamProject { exprs: [bid.auction, bid.bidder, (0.908:Decimal * bid.price::Decimal) as $expr1, bid.date_time, bid._row_id] } - └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } + └─StreamProject { exprs: [bid.auction, bid.bidder, (0.908:Decimal * bid.price::Decimal) as $expr1, bid.date_time, bid._row_id] } + └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } stream_plan: |- StreamMaterialize { columns: [auction, bidder, price, date_time, bid._row_id(hidden)], stream_key: [bid._row_id], pk_columns: [bid._row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [bid.auction, bid.bidder, (0.908:Decimal * bid.price::Decimal) as $expr1, bid.date_time, bid._row_id] } @@ -109,21 +107,20 @@ sql: SELECT auction, price FROM bid WHERE auction = 1007 OR auction = 1020 OR auction = 2001 OR auction = 2019 OR auction = 2087; batch_plan: |- BatchExchange { order: [], dist: Single } - └─BatchFilter { predicate: (((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR (bid.auction = 2001:Int32)) OR (bid.auction = 2019:Int32)) OR (bid.auction = 2087:Int32)) } + └─BatchFilter { predicate: ((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR ((bid.auction = 2001:Int32) OR (bid.auction = 2019:Int32))) OR (bid.auction = 2087:Int32)) } └─BatchScan { table: bid, columns: [bid.auction, bid.price], distribution: SomeShard } sink_plan: |- StreamSink { type: append-only, columns: [auction, price, bid._row_id(hidden)] } - └─StreamExchange { dist: Single } - └─StreamFilter { predicate: (((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR (bid.auction = 2001:Int32)) OR (bid.auction = 2019:Int32)) OR (bid.auction = 2087:Int32)) } - └─StreamTableScan { table: bid, columns: [bid.auction, bid.price, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } + └─StreamFilter { predicate: ((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR ((bid.auction = 2001:Int32) OR (bid.auction = 2019:Int32))) OR (bid.auction = 2087:Int32)) } + └─StreamTableScan { table: bid, columns: [bid.auction, bid.price, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } stream_plan: |- StreamMaterialize { columns: [auction, price, bid._row_id(hidden)], stream_key: [bid._row_id], pk_columns: [bid._row_id], pk_conflict: NoCheck } - └─StreamFilter { predicate: (((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR (bid.auction = 2001:Int32)) OR (bid.auction = 2019:Int32)) OR (bid.auction = 2087:Int32)) } + └─StreamFilter { predicate: ((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR ((bid.auction = 2001:Int32) OR (bid.auction = 2019:Int32))) OR (bid.auction = 2087:Int32)) } └─StreamTableScan { table: bid, columns: [bid.auction, bid.price, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } stream_dist_plan: |+ Fragment 0 StreamMaterialize { columns: [auction, price, bid._row_id(hidden)], stream_key: [bid._row_id], pk_columns: [bid._row_id], pk_conflict: NoCheck } { tables: [ Materialize: 4294967294 ] } - └── StreamFilter { predicate: (((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR (bid.auction = 2001:Int32)) OR (bid.auction = 2019:Int32)) OR (bid.auction = 2087:Int32)) } + └── StreamFilter { predicate: ((((bid.auction = 1007:Int32) OR (bid.auction = 1020:Int32)) OR ((bid.auction = 2001:Int32) OR (bid.auction = 2019:Int32))) OR (bid.auction = 2087:Int32)) } └── StreamTableScan { table: bid, columns: [bid.auction, bid.price, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } ├── tables: [ StreamScan: 0 ] ├── Upstream @@ -751,7 +748,7 @@ │ └─StreamProject { exprs: [person.id, person.name, $expr1, ($expr1 + '00:00:10':Interval) as $expr2] } │ └─StreamProject { exprs: [person.id, person.name, person.date_time, TumbleStart(person.date_time, '00:00:10':Interval) as $expr1] } │ └─StreamTableScan { table: person, columns: [person.id, person.name, person.date_time], stream_scan_type: ArrangementBackfill, stream_key: [person.id], pk: [id], dist: UpstreamHashShard(person.id) } - └─StreamProject { exprs: [auction.seller, $expr3, $expr4] } + └─StreamProject { exprs: [auction.seller, $expr3, $expr4], noop_update_hint: true } └─StreamHashAgg { group_key: [auction.seller, $expr3, $expr4], aggs: [count] } └─StreamExchange { dist: HashShard(auction.seller, $expr3, $expr4) } └─StreamProject { exprs: [auction.seller, $expr3, ($expr3 + '00:00:10':Interval) as $expr4, auction.id] } @@ -767,7 +764,7 @@ StreamHashJoin { type: Inner, predicate: person.id = auction.seller AND $expr1 = $expr3 AND $expr2 = $expr4, output: [person.id, internal_last_seen_value(person.name), $expr1, $expr2, auction.seller, $expr3, $expr4] } ├── tables: [ HashJoinLeft: 0, HashJoinDegreeLeft: 1, HashJoinRight: 2, HashJoinDegreeRight: 3 ] ├── StreamExchange Hash([0, 1, 2]) from 2 - └── StreamProject { exprs: [auction.seller, $expr3, $expr4] } + └── StreamProject { exprs: [auction.seller, $expr3, $expr4], noop_update_hint: true } └── StreamHashAgg { group_key: [auction.seller, $expr3, $expr4], aggs: [count] } { tables: [ HashAggState: 6 ] } └── StreamExchange Hash([0, 1, 2]) from 3 @@ -911,9 +908,8 @@ └─BatchScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time], distribution: SomeShard } sink_plan: |- StreamSink { type: append-only, columns: [auction, bidder, price, date_time, date, time, bid._row_id(hidden)] } - └─StreamExchange { dist: Single } - └─StreamProject { exprs: [bid.auction, bid.bidder, bid.price, bid.date_time, ToChar(bid.date_time, 'YYYY-MM-DD':Varchar) as $expr1, ToChar(bid.date_time, 'HH:MI':Varchar) as $expr2, bid._row_id] } - └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } + └─StreamProject { exprs: [bid.auction, bid.bidder, bid.price, bid.date_time, ToChar(bid.date_time, 'YYYY-MM-DD':Varchar) as $expr1, ToChar(bid.date_time, 'HH:MI':Varchar) as $expr2, bid._row_id] } + └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } stream_plan: |- StreamMaterialize { columns: [auction, bidder, price, date_time, date, time, bid._row_id(hidden)], stream_key: [bid._row_id], pk_columns: [bid._row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [bid.auction, bid.bidder, bid.price, bid.date_time, ToChar(bid.date_time, 'YYYY-MM-DD':Varchar) as $expr1, ToChar(bid.date_time, 'HH:MI':Varchar) as $expr2, bid._row_id] } @@ -1083,10 +1079,9 @@ └─BatchScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid.extra], distribution: SomeShard } sink_plan: |- StreamSink { type: append-only, columns: [auction, bidder, price, bidtimetype, date_time, extra, bid._row_id(hidden)] } - └─StreamExchange { dist: Single } - └─StreamProject { exprs: [bid.auction, bid.bidder, (0.908:Decimal * bid.price::Decimal) as $expr1, Case(((Extract('HOUR':Varchar, bid.date_time) >= 8:Decimal) AND (Extract('HOUR':Varchar, bid.date_time) <= 18:Decimal)), 'dayTime':Varchar, ((Extract('HOUR':Varchar, bid.date_time) <= 6:Decimal) OR (Extract('HOUR':Varchar, bid.date_time) >= 20:Decimal)), 'nightTime':Varchar, 'otherTime':Varchar) as $expr2, bid.date_time, bid.extra, bid._row_id] } - └─StreamFilter { predicate: ((0.908:Decimal * bid.price::Decimal) > 1000000:Decimal) AND ((0.908:Decimal * bid.price::Decimal) < 50000000:Decimal) } - └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid.extra, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } + └─StreamProject { exprs: [bid.auction, bid.bidder, (0.908:Decimal * bid.price::Decimal) as $expr1, Case(((Extract('HOUR':Varchar, bid.date_time) >= 8:Decimal) AND (Extract('HOUR':Varchar, bid.date_time) <= 18:Decimal)), 'dayTime':Varchar, ((Extract('HOUR':Varchar, bid.date_time) <= 6:Decimal) OR (Extract('HOUR':Varchar, bid.date_time) >= 20:Decimal)), 'nightTime':Varchar, 'otherTime':Varchar) as $expr2, bid.date_time, bid.extra, bid._row_id] } + └─StreamFilter { predicate: ((0.908:Decimal * bid.price::Decimal) > 1000000:Decimal) AND ((0.908:Decimal * bid.price::Decimal) < 50000000:Decimal) } + └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.date_time, bid.extra, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } stream_plan: |- StreamMaterialize { columns: [auction, bidder, price, bidtimetype, date_time, extra, bid._row_id(hidden)], stream_key: [bid._row_id], pk_columns: [bid._row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [bid.auction, bid.bidder, (0.908:Decimal * bid.price::Decimal) as $expr1, Case(((Extract('HOUR':Varchar, bid.date_time) >= 8:Decimal) AND (Extract('HOUR':Varchar, bid.date_time) <= 18:Decimal)), 'dayTime':Varchar, ((Extract('HOUR':Varchar, bid.date_time) <= 6:Decimal) OR (Extract('HOUR':Varchar, bid.date_time) >= 20:Decimal)), 'nightTime':Varchar, 'otherTime':Varchar) as $expr2, bid.date_time, bid.extra, bid._row_id] } @@ -1747,9 +1742,8 @@ └─BatchScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.channel, bid.url], distribution: SomeShard } sink_plan: |- StreamSink { type: append-only, columns: [auction, bidder, price, channel, dir1, dir2, dir3, bid._row_id(hidden)] } - └─StreamExchange { dist: Single } - └─StreamProject { exprs: [bid.auction, bid.bidder, bid.price, bid.channel, SplitPart(bid.url, '/':Varchar, 4:Int32) as $expr1, SplitPart(bid.url, '/':Varchar, 5:Int32) as $expr2, SplitPart(bid.url, '/':Varchar, 6:Int32) as $expr3, bid._row_id] } - └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.channel, bid.url, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } + └─StreamProject { exprs: [bid.auction, bid.bidder, bid.price, bid.channel, SplitPart(bid.url, '/':Varchar, 4:Int32) as $expr1, SplitPart(bid.url, '/':Varchar, 5:Int32) as $expr2, SplitPart(bid.url, '/':Varchar, 6:Int32) as $expr3, bid._row_id] } + └─StreamTableScan { table: bid, columns: [bid.auction, bid.bidder, bid.price, bid.channel, bid.url, bid._row_id], stream_scan_type: ArrangementBackfill, stream_key: [bid._row_id], pk: [_row_id], dist: UpstreamHashShard(bid._row_id) } stream_plan: |- StreamMaterialize { columns: [auction, bidder, price, channel, dir1, dir2, dir3, bid._row_id(hidden)], stream_key: [bid._row_id], pk_columns: [bid._row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [bid.auction, bid.bidder, bid.price, bid.channel, SplitPart(bid.url, '/':Varchar, 4:Int32) as $expr1, SplitPart(bid.url, '/':Varchar, 5:Int32) as $expr2, SplitPart(bid.url, '/':Varchar, 6:Int32) as $expr3, bid._row_id] } diff --git a/src/frontend/planner_test/tests/testdata/output/nexmark_source.yaml b/src/frontend/planner_test/tests/testdata/output/nexmark_source.yaml index 823fa85459df7..35713c9682a35 100644 --- a/src/frontend/planner_test/tests/testdata/output/nexmark_source.yaml +++ b/src/frontend/planner_test/tests/testdata/output/nexmark_source.yaml @@ -116,13 +116,13 @@ sql: SELECT auction, price FROM bid WHERE auction = 1007 OR auction = 1020 OR auction = 2001 OR auction = 2019 OR auction = 2087; batch_plan: |- BatchExchange { order: [], dist: Single } - └─BatchFilter { predicate: (((((auction = 1007:Int32) OR (auction = 1020:Int32)) OR (auction = 2001:Int32)) OR (auction = 2019:Int32)) OR (auction = 2087:Int32)) } + └─BatchFilter { predicate: ((((auction = 1007:Int32) OR (auction = 1020:Int32)) OR ((auction = 2001:Int32) OR (auction = 2019:Int32))) OR (auction = 2087:Int32)) } └─BatchProject { exprs: [auction, price] } └─BatchSource { source: bid, columns: [auction, bidder, price, channel, url, date_time, extra, _row_id] } stream_plan: |- StreamMaterialize { columns: [auction, price, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [auction, price, _row_id] } - └─StreamFilter { predicate: (((((auction = 1007:Int32) OR (auction = 1020:Int32)) OR (auction = 2001:Int32)) OR (auction = 2019:Int32)) OR (auction = 2087:Int32)) } + └─StreamFilter { predicate: ((((auction = 1007:Int32) OR (auction = 1020:Int32)) OR ((auction = 2001:Int32) OR (auction = 2019:Int32))) OR (auction = 2087:Int32)) } └─StreamRowIdGen { row_id_index: 7 } └─StreamSource { source: bid, columns: [auction, bidder, price, channel, url, date_time, extra, _row_id] } stream_dist_plan: |+ @@ -130,7 +130,7 @@ StreamMaterialize { columns: [auction, price, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } ├── tables: [ Materialize: 4294967294 ] └── StreamProject { exprs: [auction, price, _row_id] } - └── StreamFilter { predicate: (((((auction = 1007:Int32) OR (auction = 1020:Int32)) OR (auction = 2001:Int32)) OR (auction = 2019:Int32)) OR (auction = 2087:Int32)) } + └── StreamFilter { predicate: ((((auction = 1007:Int32) OR (auction = 1020:Int32)) OR ((auction = 2001:Int32) OR (auction = 2019:Int32))) OR (auction = 2087:Int32)) } └── StreamRowIdGen { row_id_index: 7 } └── StreamSource { source: bid, columns: [auction, bidder, price, channel, url, date_time, extra, _row_id] } { tables: [ Source: 0 ] } diff --git a/src/frontend/planner_test/tests/testdata/output/nexmark_temporal_filter.yaml b/src/frontend/planner_test/tests/testdata/output/nexmark_temporal_filter.yaml index f77e975780c8a..d5d948e5b507c 100644 --- a/src/frontend/planner_test/tests/testdata/output/nexmark_temporal_filter.yaml +++ b/src/frontend/planner_test/tests/testdata/output/nexmark_temporal_filter.yaml @@ -116,7 +116,7 @@ StreamMaterialize { columns: [auction, price, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [Field(bid, 0:Int32) as $expr3, Field(bid, 2:Int32) as $expr4, _row_id] } └─StreamDynamicFilter { predicate: ($expr1 > $expr2), output_watermarks: [$expr1], output: [event_type, person, auction, bid, $expr1, _row_id], cleaned_by_watermark: true } - ├─StreamFilter { predicate: (((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR (Field(bid, 0:Int32) = 2001:Int32)) OR (Field(bid, 0:Int32) = 2019:Int32)) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } + ├─StreamFilter { predicate: ((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR ((Field(bid, 0:Int32) = 2001:Int32) OR (Field(bid, 0:Int32) = 2019:Int32))) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } │ └─StreamRowIdGen { row_id_index: 5 } │ └─StreamProject { exprs: [event_type, person, auction, bid, Proctime as $expr1, _row_id], output_watermarks: [$expr1] } │ └─StreamSource { source: nexmark, columns: [event_type, person, auction, bid, _row_id] } @@ -128,7 +128,7 @@ StreamMaterialize { columns: [auction, price, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } { tables: [ Materialize: 4294967294 ] } └── StreamProject { exprs: [Field(bid, 0:Int32) as $expr3, Field(bid, 2:Int32) as $expr4, _row_id] } └── StreamDynamicFilter { predicate: ($expr1 > $expr2), output_watermarks: [$expr1], output: [event_type, person, auction, bid, $expr1, _row_id], cleaned_by_watermark: true } { tables: [ DynamicFilterLeft: 0, DynamicFilterRight: 1 ] } - ├── StreamFilter { predicate: (((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR (Field(bid, 0:Int32) = 2001:Int32)) OR (Field(bid, 0:Int32) = 2019:Int32)) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } + ├── StreamFilter { predicate: ((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR ((Field(bid, 0:Int32) = 2001:Int32) OR (Field(bid, 0:Int32) = 2019:Int32))) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } │ └── StreamRowIdGen { row_id_index: 5 } │ └── StreamProject { exprs: [event_type, person, auction, bid, Proctime as $expr1, _row_id], output_watermarks: [$expr1] } │ └── StreamSource { source: nexmark, columns: [event_type, person, auction, bid, _row_id] } { tables: [ Source: 2 ] } diff --git a/src/frontend/planner_test/tests/testdata/output/nexmark_watermark.yaml b/src/frontend/planner_test/tests/testdata/output/nexmark_watermark.yaml index 6dd731cffffb0..f065ba33c252d 100644 --- a/src/frontend/planner_test/tests/testdata/output/nexmark_watermark.yaml +++ b/src/frontend/planner_test/tests/testdata/output/nexmark_watermark.yaml @@ -77,13 +77,13 @@ batch_plan: |- BatchExchange { order: [], dist: Single } └─BatchProject { exprs: [Field(bid, 0:Int32) as $expr2, Field(bid, 2:Int32) as $expr3] } - └─BatchFilter { predicate: (((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR (Field(bid, 0:Int32) = 2001:Int32)) OR (Field(bid, 0:Int32) = 2019:Int32)) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } + └─BatchFilter { predicate: ((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR ((Field(bid, 0:Int32) = 2001:Int32) OR (Field(bid, 0:Int32) = 2019:Int32))) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } └─BatchProject { exprs: [event_type, person, auction, bid, Case((event_type = 0:Int32), Field(person, 6:Int32), (event_type = 1:Int32), Field(auction, 5:Int32), Field(bid, 5:Int32)) as $expr1, _row_id] } └─BatchSource { source: nexmark, columns: [event_type, person, auction, bid, _row_id] } stream_plan: |- StreamMaterialize { columns: [auction, price, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [Field(bid, 0:Int32) as $expr2, Field(bid, 2:Int32) as $expr3, _row_id] } - └─StreamFilter { predicate: (((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR (Field(bid, 0:Int32) = 2001:Int32)) OR (Field(bid, 0:Int32) = 2019:Int32)) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } + └─StreamFilter { predicate: ((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR ((Field(bid, 0:Int32) = 2001:Int32) OR (Field(bid, 0:Int32) = 2019:Int32))) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } └─StreamRowIdGen { row_id_index: 5 } └─StreamWatermarkFilter { watermark_descs: [Desc { column: $expr1, expr: ($expr1 - '00:00:04':Interval) }], output_watermarks: [$expr1] } └─StreamProject { exprs: [event_type, person, auction, bid, Case((event_type = 0:Int32), Field(person, 6:Int32), (event_type = 1:Int32), Field(auction, 5:Int32), Field(bid, 5:Int32)) as $expr1, _row_id] } @@ -92,7 +92,7 @@ Fragment 0 StreamMaterialize { columns: [auction, price, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } { tables: [ Materialize: 4294967294 ] } └── StreamProject { exprs: [Field(bid, 0:Int32) as $expr2, Field(bid, 2:Int32) as $expr3, _row_id] } - └── StreamFilter { predicate: (((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR (Field(bid, 0:Int32) = 2001:Int32)) OR (Field(bid, 0:Int32) = 2019:Int32)) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } + └── StreamFilter { predicate: ((((Field(bid, 0:Int32) = 1007:Int32) OR (Field(bid, 0:Int32) = 1020:Int32)) OR ((Field(bid, 0:Int32) = 2001:Int32) OR (Field(bid, 0:Int32) = 2019:Int32))) OR (Field(bid, 0:Int32) = 2087:Int32)) AND (event_type = 2:Int32) } └── StreamRowIdGen { row_id_index: 5 } └── StreamWatermarkFilter { watermark_descs: [Desc { column: $expr1, expr: ($expr1 - '00:00:04':Interval) }], output_watermarks: [$expr1] } { tables: [ WatermarkFilter: 0 ] } └── StreamProject { exprs: [event_type, person, auction, bid, Case((event_type = 0:Int32), Field(person, 6:Int32), (event_type = 1:Int32), Field(auction, 5:Int32), Field(bid, 5:Int32)) as $expr1, _row_id] } diff --git a/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml b/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml index fbdccf685ca2e..3449606ad97ba 100644 --- a/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml +++ b/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml @@ -73,11 +73,11 @@ └─LogicalProject { exprs: [t.x, t._row_id] } └─LogicalScan { table: t, columns: [t.x, t._row_id] } batch_error: |- - Feature is not yet implemented: Window function with empty PARTITION BY is not supported yet - No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml + Feature is not yet implemented: Window function with empty PARTITION BY is not supported because of potential bad performance. If you really need this, please workaround with something like `PARTITION BY 1::int`. + Tracking issue: https://github.com/risingwavelabs/risingwave/issues/11505 stream_error: |- - Feature is not yet implemented: Window function with empty PARTITION BY is not supported yet - No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml + Feature is not yet implemented: Window function with empty PARTITION BY is not supported because of potential bad performance. If you really need this, please workaround with something like `PARTITION BY 1::int`. + Tracking issue: https://github.com/risingwavelabs/risingwave/issues/11505 - id: lead with offset argument and empty over clause sql: | create table t(x int); @@ -88,11 +88,11 @@ └─LogicalProject { exprs: [t.x, t._row_id, 2:Int32] } └─LogicalScan { table: t, columns: [t.x, t._row_id] } batch_error: |- - Feature is not yet implemented: Window function with empty PARTITION BY is not supported yet - No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml + Feature is not yet implemented: Window function with empty PARTITION BY is not supported because of potential bad performance. If you really need this, please workaround with something like `PARTITION BY 1::int`. + Tracking issue: https://github.com/risingwavelabs/risingwave/issues/11505 stream_error: |- - Feature is not yet implemented: Window function with empty PARTITION BY is not supported yet - No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml + Feature is not yet implemented: Window function with empty PARTITION BY is not supported because of potential bad performance. If you really need this, please workaround with something like `PARTITION BY 1::int`. + Tracking issue: https://github.com/risingwavelabs/risingwave/issues/11505 - id: lead with non-const offset argument and empty over clause sql: | create table t(x int); @@ -164,7 +164,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - id: aggregate with over clause, without ORDER BY and frame definition sql: | create table t(x int, y int, z int, w int); @@ -1235,3 +1235,115 @@ Caused by these errors (recent errors listed first): 1: Expr error 2: for frame order column of type `timestamptz`, offset should not have non-zero `month` and `day` +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + first_value(i) over (partition by bi order by i session with gap 10) as col1, + first_value(bi) over (partition by i order by bi session with gap 10) as col2, + first_value(i) over (partition by bi order by d session with gap 1.5) as col3, + first_value(i) over (partition by bi order by f session with gap 1.5) as col4, + -- first_value(i) over (partition by bi order by da session with gap '1 day') as col5, -- `date` not supported yet + -- first_value(i) over (partition by bi order by t session with gap '1 min') as col6, -- `time` not supported yet + first_value(i) over (partition by bi order by ts session with gap '1 day 1 hour') as col7, + first_value(i) over (partition by bi order by tstz session with gap '1 min') as col8 + from t; + logical_plan: |- + LogicalProject { exprs: [first_value, first_value, first_value, first_value, first_value, first_value] } + └─LogicalOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.i ASC SESSION WITH GAP 10), first_value(t.bi) OVER(PARTITION BY t.i ORDER BY t.bi ASC SESSION WITH GAP 10), first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.d ASC SESSION WITH GAP 1.5), first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.f ASC SESSION WITH GAP 1.5), first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 1 day 01:00:00), first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.tstz ASC SESSION WITH GAP 00:01:00)] } + └─LogicalProject { exprs: [t.i, t.bi, t.d, t.f, t.da, t.t, t.ts, t.tstz, t.itv, t._row_id] } + └─LogicalScan { table: t, columns: [t.i, t.bi, t.d, t.f, t.da, t.t, t.ts, t.tstz, t.itv, t._row_id] } + optimized_logical_plan_for_stream: |- + LogicalProject { exprs: [first_value, first_value, first_value, first_value, first_value, first_value] } + └─LogicalOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.tstz ASC SESSION WITH GAP 00:01:00)] } + └─LogicalProject { exprs: [t.i, t.bi, t.tstz, first_value, first_value, first_value, first_value, first_value] } + └─LogicalOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 1 day 01:00:00)] } + └─LogicalProject { exprs: [t.i, t.bi, t.ts, t.tstz, first_value, first_value, first_value, first_value] } + └─LogicalOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.f ASC SESSION WITH GAP 1.5)] } + └─LogicalProject { exprs: [t.i, t.bi, t.f, t.ts, t.tstz, first_value, first_value, first_value] } + └─LogicalOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.d ASC SESSION WITH GAP 1.5)] } + └─LogicalOverWindow { window_functions: [first_value(t.bi) OVER(PARTITION BY t.i ORDER BY t.bi ASC SESSION WITH GAP 10)] } + └─LogicalOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.i ASC SESSION WITH GAP 10)] } + └─LogicalScan { table: t, columns: [t.i, t.bi, t.d, t.f, t.ts, t.tstz] } + batch_plan: |- + BatchExchange { order: [], dist: Single } + └─BatchProject { exprs: [first_value, first_value, first_value, first_value, first_value, first_value] } + └─BatchOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.tstz ASC SESSION WITH GAP 00:01:00)] } + └─BatchSort { order: [t.bi ASC, t.tstz ASC] } + └─BatchProject { exprs: [t.i, t.bi, t.tstz, first_value, first_value, first_value, first_value, first_value] } + └─BatchOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 1 day 01:00:00)] } + └─BatchSort { order: [t.bi ASC, t.ts ASC] } + └─BatchProject { exprs: [t.i, t.bi, t.ts, t.tstz, first_value, first_value, first_value, first_value] } + └─BatchOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.f ASC SESSION WITH GAP 1.5)] } + └─BatchSort { order: [t.bi ASC, t.f ASC] } + └─BatchProject { exprs: [t.i, t.bi, t.f, t.ts, t.tstz, first_value, first_value, first_value] } + └─BatchOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.d ASC SESSION WITH GAP 1.5)] } + └─BatchExchange { order: [t.bi ASC, t.d ASC], dist: HashShard(t.bi) } + └─BatchSort { order: [t.bi ASC, t.d ASC] } + └─BatchOverWindow { window_functions: [first_value(t.bi) OVER(PARTITION BY t.i ORDER BY t.bi ASC SESSION WITH GAP 10)] } + └─BatchExchange { order: [t.i ASC, t.bi ASC], dist: HashShard(t.i) } + └─BatchSort { order: [t.i ASC, t.bi ASC] } + └─BatchOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.i ASC SESSION WITH GAP 10)] } + └─BatchExchange { order: [t.bi ASC, t.i ASC], dist: HashShard(t.bi) } + └─BatchSort { order: [t.bi ASC, t.i ASC] } + └─BatchScan { table: t, columns: [t.i, t.bi, t.d, t.f, t.ts, t.tstz], distribution: SomeShard } + stream_error: |- + Feature is not yet implemented: Session frame is not yet supported in general streaming mode. Please consider using Emit-On-Window-Close mode. + No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml +- sql: | + create table t (i int, bi bigint, ts timestamp, watermark for ts as ts - interval '1 minute') append only; + select + first_value(i) over (partition by bi order by ts session with gap '10 minutes') as window_start, + last_value(i) over (partition by bi order by ts session with gap '10 minutes') as window_end + from t; + logical_plan: |- + LogicalProject { exprs: [first_value, last_value] } + └─LogicalOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 00:10:00), last_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 00:10:00)] } + └─LogicalProject { exprs: [t.i, t.bi, t.ts, t._row_id] } + └─LogicalScan { table: t, columns: [t.i, t.bi, t.ts, t._row_id] } + batch_plan: |- + BatchExchange { order: [], dist: Single } + └─BatchProject { exprs: [first_value, last_value] } + └─BatchOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 00:10:00), last_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 00:10:00)] } + └─BatchExchange { order: [t.bi ASC, t.ts ASC], dist: HashShard(t.bi) } + └─BatchSort { order: [t.bi ASC, t.ts ASC] } + └─BatchScan { table: t, columns: [t.i, t.bi, t.ts], distribution: SomeShard } + eowc_stream_plan: |- + StreamMaterialize { columns: [window_start, window_end, t._row_id(hidden), t.bi(hidden)], stream_key: [t._row_id, t.bi], pk_columns: [t._row_id, t.bi], pk_conflict: NoCheck } + └─StreamProject { exprs: [first_value, last_value, t._row_id, t.bi] } + └─StreamEowcOverWindow { window_functions: [first_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 00:10:00), last_value(t.i) OVER(PARTITION BY t.bi ORDER BY t.ts ASC SESSION WITH GAP 00:10:00)] } + └─StreamEowcSort { sort_column: t.ts } + └─StreamExchange { dist: HashShard(t.bi) } + └─StreamTableScan { table: t, columns: [t.i, t.bi, t.ts, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + count(*) over (partition by 1::int order by da session with gap '1 day') -- `date` not supported yet + from t; + binder_error: | + Failed to bind expression: count(*) OVER (PARTITION BY CAST(1 AS INT) ORDER BY da SESSION WITH GAP '1 day') + + Caused by: + Feature is not yet implemented: `SESSION` frame with offset of type `date` is not implemented yet, please manually cast the `ORDER BY` column to `timestamp` + No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + count(*) over (partition by 1::int order by t session with gap '1 min') -- `time` not supported yet + from t; + binder_error: | + Failed to bind expression: count(*) OVER (PARTITION BY CAST(1 AS INT) ORDER BY t SESSION WITH GAP '1 min') + + Caused by: + Feature is not yet implemented: `SESSION` frame with offset of type `time without time zone` is not implemented yet, please manually cast the `ORDER BY` column to `timestamp` + No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml +- sql: | + create table t (i int, bi bigint, d decimal, f float, da date, t time, ts timestamp, tstz timestamptz, itv interval); + select + count(*) over (partition by 1::int order by tstz session with gap '1 day 1 hour') -- `timestamptz` +/- 'x month x day' not supported yet + from t; + binder_error: | + Failed to bind expression: count(*) OVER (PARTITION BY CAST(1 AS INT) ORDER BY tstz SESSION WITH GAP '1 day 1 hour') + + Caused by these errors (recent errors listed first): + 1: Expr error + 2: for session order column of type `timestamptz`, gap should not have non-zero `month` and `day` diff --git a/src/frontend/planner_test/tests/testdata/output/pk_derive.yaml b/src/frontend/planner_test/tests/testdata/output/pk_derive.yaml index 5c8e87aea559f..7392c88bde3cc 100644 --- a/src/frontend/planner_test/tests/testdata/output/pk_derive.yaml +++ b/src/frontend/planner_test/tests/testdata/output/pk_derive.yaml @@ -77,7 +77,7 @@ └─LogicalScan { table: t, columns: [t.v1, t.v2, t.v3] } stream_plan: |- StreamMaterialize { columns: [v1, v2, v3], stream_key: [v1, v2, v3], pk_columns: [v1, v2, v3], pk_conflict: NoCheck } - └─StreamProject { exprs: [t.v1, t.v2, t.v3] } + └─StreamProject { exprs: [t.v1, t.v2, t.v3], noop_update_hint: true } └─StreamHashAgg { group_key: [t.v1, t.v2, t.v3], aggs: [count] } └─StreamExchange { dist: HashShard(t.v1, t.v2, t.v3) } └─StreamTableScan { table: t, columns: [t.v1, t.v2, t.v3, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } diff --git a/src/frontend/planner_test/tests/testdata/output/project_set.yaml b/src/frontend/planner_test/tests/testdata/output/project_set.yaml index 890480493af92..8373ea5187fca 100644 --- a/src/frontend/planner_test/tests/testdata/output/project_set.yaml +++ b/src/frontend/planner_test/tests/testdata/output/project_set.yaml @@ -118,7 +118,7 @@ └─BatchScan { table: t, columns: [t.x], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [unnest], stream_key: [unnest], pk_columns: [unnest], pk_conflict: NoCheck } - └─StreamProject { exprs: [Unnest($0)] } + └─StreamProject { exprs: [Unnest($0)], noop_update_hint: true } └─StreamHashAgg { group_key: [Unnest($0)], aggs: [count] } └─StreamExchange { dist: HashShard(Unnest($0)) } └─StreamProjectSet { select_list: [Unnest($0), $1] } diff --git a/src/frontend/planner_test/tests/testdata/output/range_scan.yaml b/src/frontend/planner_test/tests/testdata/output/range_scan.yaml index b54dc13b5470b..95a4315481ef9 100644 --- a/src/frontend/planner_test/tests/testdata/output/range_scan.yaml +++ b/src/frontend/planner_test/tests/testdata/output/range_scan.yaml @@ -35,8 +35,9 @@ batch_error: | Expr error - Caused by: - Division by zero + Caused by these errors (recent errors listed first): + 1: error while evaluating expression `general_div('1', '0')` + 2: Division by zero - before: - create_table_and_mv sql: | @@ -44,8 +45,9 @@ batch_error: | Expr error - Caused by: - Numeric out of range + Caused by these errors (recent errors listed first): + 1: error while evaluating expression `general_add('2147483647', '1')` + 2: Numeric out of range - before: - create_table_and_mv sql: | @@ -53,8 +55,9 @@ batch_error: | Expr error - Caused by: - Parse error: bigint invalid digit found in string + Caused by these errors (recent errors listed first): + 1: error while evaluating expression `str_parse('a')` + 2: Parse error: bigint invalid digit found in string - before: - create_table_and_mv sql: | @@ -62,8 +65,9 @@ batch_error: | Expr error - Caused by: - Parse error: bigint invalid digit found in string + Caused by these errors (recent errors listed first): + 1: error while evaluating expression `str_parse('a')` + 2: Parse error: bigint invalid digit found in string - before: - create_table_and_mv sql: | @@ -147,8 +151,9 @@ batch_error: | Expr error - Caused by: - Parse error: bigint invalid digit found in string + Caused by these errors (recent errors listed first): + 1: error while evaluating expression `str_parse('43.0')` + 2: Parse error: bigint invalid digit found in string - before: - create_table_and_mv sql: | diff --git a/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml b/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml index 18e0e18726d38..93872d5848bd6 100644 --- a/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml +++ b/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml @@ -1,14 +1,33 @@ # This file is automatically generated. See `src/frontend/planner_test/README.md` for more information. - name: basic sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT * FROM t1; - planner_error: |- - Feature is not yet implemented: recursive CTE is not supported - Tracking issue: https://github.com/risingwavelabs/risingwave/issues/15135 + logical_plan: |- + LogicalProject { exprs: [$expr1] } + └─LogicalRecursiveUnion { id: 4 } + ├─LogicalProject { exprs: [1:Int32] } + │ └─LogicalValues { rows: [[]], schema: Schema { fields: [] } } + └─LogicalProject { exprs: [(1:Int32 + 1:Int32) as $expr1] } + └─LogicalFilter { predicate: (1:Int32 < 10:Int32) } + └─LogicalCteRef { share_id: 0 } - name: output column follows lhs sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a FROM t1; - planner_error: |- - Feature is not yet implemented: recursive CTE is not supported - Tracking issue: https://github.com/risingwavelabs/risingwave/issues/15135 + logical_plan: |- + LogicalProject { exprs: [$expr1] } + └─LogicalRecursiveUnion { id: 4 } + ├─LogicalProject { exprs: [1:Int32] } + │ └─LogicalValues { rows: [[]], schema: Schema { fields: [] } } + └─LogicalProject { exprs: [(1:Int32 + 1:Int32) as $expr1] } + └─LogicalFilter { predicate: (1:Int32 < 10:Int32) } + └─LogicalCteRef { share_id: 0 } +- name: with normal column + sql: WITH RECURSIVE t(a) AS (VALUES(1) UNION ALL SELECT a + 1 FROM t WHERE a < 100) SELECT * FROM t; + logical_plan: |- + LogicalProject { exprs: [$expr1] } + └─LogicalRecursiveUnion { id: 3 } + ├─LogicalValues { rows: [[1:Int32]], schema: Schema { fields: [*VALUES*_0.column_0:Int32] } } + └─LogicalProject { exprs: [(*VALUES*_0.column_0 + 1:Int32) as $expr1] } + └─LogicalFilter { predicate: (*VALUES*_0.column_0 < 100:Int32) } + └─LogicalCteRef { share_id: 0 } - name: name a is leaked outside sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a; binder_error: | diff --git a/src/frontend/planner_test/tests/testdata/output/share.yaml b/src/frontend/planner_test/tests/testdata/output/share.yaml index cfe5d841cb2c1..2cf3aee9fe043 100644 --- a/src/frontend/planner_test/tests/testdata/output/share.yaml +++ b/src/frontend/planner_test/tests/testdata/output/share.yaml @@ -222,12 +222,12 @@ └─StreamStatelessSimpleAgg { aggs: [count] } └─StreamHashJoin { type: Inner, predicate: t.a = t.a, output: all } ├─StreamShare { id: 4 } - │ └─StreamProject { exprs: [t.a] } + │ └─StreamProject { exprs: [t.a], noop_update_hint: true } │ └─StreamHashAgg { group_key: [t.a], aggs: [count] } │ └─StreamExchange { dist: HashShard(t.a) } │ └─StreamTableScan { table: t, columns: [t.a, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamShare { id: 4 } - └─StreamProject { exprs: [t.a] } + └─StreamProject { exprs: [t.a], noop_update_hint: true } └─StreamHashAgg { group_key: [t.a], aggs: [count] } └─StreamExchange { dist: HashShard(t.a) } └─StreamTableScan { table: t, columns: [t.a, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -251,7 +251,7 @@ └── StreamExchange NoShuffle from 4 Fragment 2 - StreamProject { exprs: [t.a] } + StreamProject { exprs: [t.a], noop_update_hint: true } └── StreamHashAgg { group_key: [t.a], aggs: [count] } { tables: [ HashAggState: 5 ] } └── StreamExchange Hash([0]) from 3 diff --git a/src/frontend/planner_test/tests/testdata/output/shared_source.yml b/src/frontend/planner_test/tests/testdata/output/shared_source.yml index 5bf3739f28411..39fb6b799fb83 100644 --- a/src/frontend/planner_test/tests/testdata/output/shared_source.yml +++ b/src/frontend/planner_test/tests/testdata/output/shared_source.yml @@ -73,7 +73,7 @@ StreamMaterialize { columns: [x, y, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } └─StreamProject { exprs: [x, y, _row_id] } └─StreamRowIdGen { row_id_index: 3 } - └─StreamSource { source: s, columns: [x, y, _rw_kafka_timestamp, _row_id, _rw_kafka_partition, _rw_kafka_offset] } + └─StreamSourceScan { columns: [x, y, _rw_kafka_timestamp, _row_id, _rw_kafka_partition, _rw_kafka_offset] } with_config_map: rw_enable_shared_source: 'true' - before: diff --git a/src/frontend/planner_test/tests/testdata/output/short_circuit.yaml b/src/frontend/planner_test/tests/testdata/output/short_circuit.yaml index f870d5d09e9b6..47f1c73897a25 100644 --- a/src/frontend/planner_test/tests/testdata/output/short_circuit.yaml +++ b/src/frontend/planner_test/tests/testdata/output/short_circuit.yaml @@ -44,3 +44,10 @@ BatchExchange { order: [], dist: Single } └─BatchProject { exprs: [false:Boolean] } └─BatchScan { table: t1, columns: [t1.c1], distribution: SomeShard } +- id: short_circuit_optimize_time + sql: | + select true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true and true; + logical_plan: |- + LogicalProject { exprs: [(((((((((((((((((((((((((true:Boolean AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) AND true:Boolean) as $expr1] } + └─LogicalValues { rows: [[]], schema: Schema { fields: [] } } + batch_plan: 'BatchValues { rows: [[true:Boolean]] }' diff --git a/src/frontend/planner_test/tests/testdata/output/sink_into_table.yaml b/src/frontend/planner_test/tests/testdata/output/sink_into_table.yaml new file mode 100644 index 0000000000000..1fc6df6613a98 --- /dev/null +++ b/src/frontend/planner_test/tests/testdata/output/sink_into_table.yaml @@ -0,0 +1,14 @@ +# This file is automatically generated. See `src/frontend/planner_test/README.md` for more information. +- id: create sink into table having default expression with now() + sql: | + create table t( + x int, + y timestamptz default now(), + z timestamptz default now() - interval '1 minute' + ) append only; + create table s(x int) append only; + explain create sink ss into t from s with (type = 'append-only'); + explain_output: | + StreamProject { exprs: [s.x, Proctime as $expr1, (Proctime - '00:01:00':Interval) as $expr2, null:Serial], output_watermarks: [$expr1, $expr2] } + └─StreamSink { type: append-only, columns: [x, s._row_id(hidden)] } + └─StreamTableScan { table: s, columns: [x, _row_id] } diff --git a/src/frontend/planner_test/tests/testdata/output/subquery.yaml b/src/frontend/planner_test/tests/testdata/output/subquery.yaml index e17f2f3cf699a..e113a0aca4d1d 100644 --- a/src/frontend/planner_test/tests/testdata/output/subquery.yaml +++ b/src/frontend/planner_test/tests/testdata/output/subquery.yaml @@ -241,7 +241,7 @@ │ │ │ │ │ │ │ └─LogicalProject { exprs: [rw_system_tables.id, rw_system_tables.name, 'system table':Varchar, rw_system_tables.schema_id, rw_system_tables.owner, rw_system_tables.definition, rw_system_tables.acl] } │ │ │ │ │ │ │ └─LogicalSysScan { table: rw_system_tables, columns: [rw_system_tables.id, rw_system_tables.name, rw_system_tables.schema_id, rw_system_tables.owner, rw_system_tables.definition, rw_system_tables.acl] } │ │ │ │ │ │ └─LogicalProject { exprs: [rw_sources.id, rw_sources.name, 'source':Varchar, rw_sources.schema_id, rw_sources.owner, rw_sources.definition, rw_sources.acl] } - │ │ │ │ │ │ └─LogicalSysScan { table: rw_sources, columns: [rw_sources.id, rw_sources.name, rw_sources.schema_id, rw_sources.owner, rw_sources.connector, rw_sources.columns, rw_sources.format, rw_sources.row_encode, rw_sources.append_only, rw_sources.connection_id, rw_sources.definition, rw_sources.acl, rw_sources.initialized_at, rw_sources.created_at, rw_sources.initialized_at_cluster_version, rw_sources.created_at_cluster_version] } + │ │ │ │ │ │ └─LogicalSysScan { table: rw_sources, columns: [rw_sources.id, rw_sources.name, rw_sources.schema_id, rw_sources.owner, rw_sources.connector, rw_sources.columns, rw_sources.format, rw_sources.row_encode, rw_sources.append_only, rw_sources.associated_table_id, rw_sources.connection_id, rw_sources.definition, rw_sources.acl, rw_sources.initialized_at, rw_sources.created_at, rw_sources.initialized_at_cluster_version, rw_sources.created_at_cluster_version] } │ │ │ │ │ └─LogicalProject { exprs: [rw_indexes.id, rw_indexes.name, 'index':Varchar, rw_indexes.schema_id, rw_indexes.owner, rw_indexes.definition, rw_indexes.acl] } │ │ │ │ │ └─LogicalSysScan { table: rw_indexes, columns: [rw_indexes.id, rw_indexes.name, rw_indexes.primary_table_id, rw_indexes.key_columns, rw_indexes.include_columns, rw_indexes.schema_id, rw_indexes.owner, rw_indexes.definition, rw_indexes.acl, rw_indexes.initialized_at, rw_indexes.created_at, rw_indexes.initialized_at_cluster_version, rw_indexes.created_at_cluster_version] } │ │ │ │ └─LogicalProject { exprs: [rw_sinks.id, rw_sinks.name, 'sink':Varchar, rw_sinks.schema_id, rw_sinks.owner, rw_sinks.definition, rw_sinks.acl] } @@ -331,9 +331,9 @@ │ └─StreamHopWindow { time_col: auction.date_time, slide: 00:00:01, size: 01:00:00, output: [auction.date_time, window_start, window_end, auction._row_id] } │ └─StreamFilter { predicate: IsNotNull(auction.date_time) } │ └─StreamTableScan { table: auction, columns: [auction.date_time, auction._row_id], stream_scan_type: ArrangementBackfill, stream_key: [auction._row_id], pk: [_row_id], dist: UpstreamHashShard(auction._row_id) } - └─StreamProject { exprs: [auction.date_time] } + └─StreamProject { exprs: [auction.date_time], noop_update_hint: true } └─StreamHashAgg { group_key: [auction.date_time], aggs: [count] } - └─StreamProject { exprs: [auction.date_time] } + └─StreamProject { exprs: [auction.date_time], noop_update_hint: true } └─StreamHashAgg { group_key: [auction.date_time], aggs: [count] } └─StreamExchange { dist: HashShard(auction.date_time) } └─StreamShare { id: 3 } @@ -439,7 +439,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - sql: | SELECT 1 a, (SELECT regexp_matches('barbeque barbeque', '(bar)(beque)', 'g')) b optimized_logical_plan_for_batch: |- @@ -529,13 +529,13 @@ └─StreamProject { exprs: [t.x, sum(Unnest($0))] } └─StreamHashAgg { group_key: [t.x], aggs: [sum(Unnest($0)), count] } └─StreamHashJoin { type: LeftOuter, predicate: t.x IS NOT DISTINCT FROM t.x, output: [t.x, Unnest($0), t.x, projected_row_id] } - ├─StreamProject { exprs: [t.x] } + ├─StreamProject { exprs: [t.x], noop_update_hint: true } │ └─StreamHashAgg { group_key: [t.x], aggs: [count] } │ └─StreamExchange { dist: HashShard(t.x) } │ └─StreamTableScan { table: t, columns: [t.x, t.k], stream_scan_type: ArrangementBackfill, stream_key: [t.k], pk: [k], dist: UpstreamHashShard(t.k) } └─StreamProject { exprs: [t.x, Unnest($0), projected_row_id] } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.x] } + └─StreamProject { exprs: [t.x], noop_update_hint: true } └─StreamHashAgg { group_key: [t.x], aggs: [count] } └─StreamExchange { dist: HashShard(t.x) } └─StreamTableScan { table: t, columns: [t.x, t.k], stream_scan_type: ArrangementBackfill, stream_key: [t.k], pk: [k], dist: UpstreamHashShard(t.k) } @@ -658,7 +658,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: test over window subquery 3 sql: | CREATE TABLE integers(i INTEGER); @@ -715,7 +715,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: test over window subquery 5 sql: | CREATE TABLE integers(i INTEGER, correlated_col int); @@ -817,7 +817,7 @@ └─StreamProject { exprs: [integers.correlated_col, (count(distinct rows.k) + count(distinct rows.v)) as $expr1] } └─StreamHashAgg { group_key: [integers.correlated_col], aggs: [count(distinct rows.k), count(distinct rows.v), count] } └─StreamHashJoin { type: LeftOuter, predicate: integers.correlated_col IS NOT DISTINCT FROM rows.correlated_col, output: [integers.correlated_col, rows.k, rows.v, rows._row_id] } - ├─StreamProject { exprs: [integers.correlated_col] } + ├─StreamProject { exprs: [integers.correlated_col], noop_update_hint: true } │ └─StreamHashAgg { group_key: [integers.correlated_col], aggs: [count] } │ └─StreamExchange { dist: HashShard(integers.correlated_col) } │ └─StreamTableScan { table: integers, columns: [integers.correlated_col, integers._row_id], stream_scan_type: ArrangementBackfill, stream_key: [integers._row_id], pk: [_row_id], dist: UpstreamHashShard(integers._row_id) } diff --git a/src/frontend/planner_test/tests/testdata/output/subquery_expr.yaml b/src/frontend/planner_test/tests/testdata/output/subquery_expr.yaml index e7c8940b5a8cc..9dc9ead95f17f 100644 --- a/src/frontend/planner_test/tests/testdata/output/subquery_expr.yaml +++ b/src/frontend/planner_test/tests/testdata/output/subquery_expr.yaml @@ -316,7 +316,8 @@ LogicalProject { exprs: [b.b1, ] } └─LogicalApply { type: Inner, on: true, correlated_id: 1 } ├─LogicalScan { table: b, columns: [b.b1, b._row_id] } - └─LogicalValues { rows: [[Repeat(CorrelatedInputRef { index: 0, correlated_id: 1 }, 2:Int32)]], schema: Schema { fields: [:Varchar] } } + └─LogicalProject { exprs: [] } + └─LogicalValues { rows: [[Repeat(CorrelatedInputRef { index: 0, correlated_id: 1 }, 2:Int32)]], schema: Schema { fields: [:Varchar] } } batch_plan: |- BatchExchange { order: [], dist: Single } └─BatchProject { exprs: [b.b1, Repeat(b.b1, 2:Int32) as $expr1] } diff --git a/src/frontend/planner_test/tests/testdata/output/subquery_expr_correlated.yaml b/src/frontend/planner_test/tests/testdata/output/subquery_expr_correlated.yaml index 762f4bea8fe5b..09905835f2009 100644 --- a/src/frontend/planner_test/tests/testdata/output/subquery_expr_correlated.yaml +++ b/src/frontend/planner_test/tests/testdata/output/subquery_expr_correlated.yaml @@ -1096,7 +1096,7 @@ └─StreamProject { exprs: [t.b, count(1:Int32)] } └─StreamHashAgg { group_key: [t.b], aggs: [count(1:Int32), count] } └─StreamHashJoin { type: LeftOuter, predicate: t.b IS NOT DISTINCT FROM t2.d, output: [t.b, 1:Int32, t2._row_id] } - ├─StreamProject { exprs: [t.b] } + ├─StreamProject { exprs: [t.b], noop_update_hint: true } │ └─StreamHashAgg { group_key: [t.b], aggs: [count] } │ └─StreamExchange { dist: HashShard(t.b) } │ └─StreamTableScan { table: t, columns: [t.b, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -1132,3 +1132,993 @@ └─StreamHashAgg { group_key: [t2.d], aggs: [sum(t2.c), count(t2.c), count] } └─StreamExchange { dist: HashShard(t2.d) } └─StreamTableScan { table: t2, columns: [t2.c, t2.d, t2._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t2._row_id], pk: [_row_id], dist: UpstreamHashShard(t2._row_id) } +- name: improve multi scalar subqueries optimization time. issue 16952. case 1. + sql: | + create table t1(a int, b int); + create table t2(c int primary key, d int); + select + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col1, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col2, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col3, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col4, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col5, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col6, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col7, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col8, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col9, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col10, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col11, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col12, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col13, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col14, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col15, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col16, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col17, + COALESCE((SELECT b FROM t2 WHERE t1.a = t2.c), 0) col18 + from t1; + batch_plan: |- + BatchExchange { order: [], dist: Single } + └─BatchProject { exprs: [Coalesce(t1.b, 0:Int32) as $expr1, Coalesce(t1.b, 0:Int32) as $expr2, Coalesce(t1.b, 0:Int32) as $expr3, Coalesce(t1.b, 0:Int32) as $expr4, Coalesce(t1.b, 0:Int32) as $expr5, Coalesce(t1.b, 0:Int32) as $expr6, Coalesce(t1.b, 0:Int32) as $expr7, Coalesce(t1.b, 0:Int32) as $expr8, Coalesce(t1.b, 0:Int32) as $expr9, Coalesce(t1.b, 0:Int32) as $expr10, Coalesce(t1.b, 0:Int32) as $expr11, Coalesce(t1.b, 0:Int32) as $expr12, Coalesce(t1.b, 0:Int32) as $expr13, Coalesce(t1.b, 0:Int32) as $expr14, Coalesce(t1.b, 0:Int32) as $expr15, Coalesce(t1.b, 0:Int32) as $expr16, Coalesce(t1.b, 0:Int32) as $expr17, Coalesce(t1.b, 0:Int32) as $expr18] } + └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + └─BatchProject { exprs: [t1.a, t1.b, t1.b] } + └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: [t1.a, t1.b], lookup table: t2 } + └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + stream_plan: |- + StreamMaterialize { columns: [col1, col2, col3, col4, col5, col6, col7, col8, col9, col10, col11, col12, col13, col14, col15, col16, col17, col18, t1._row_id(hidden), t1.b(hidden), t1.a(hidden), t1.b#1(hidden), t1.b#2(hidden), t1.b#3(hidden), t1.b#4(hidden), t1.b#5(hidden), t1.b#6(hidden), t1.b#7(hidden), t1.b#8(hidden), t1.b#9(hidden), t1.b#10(hidden), t1.b#11(hidden), t1.b#12(hidden), t1.b#13(hidden), t1.b#14(hidden), t1.b#15(hidden), t1.b#16(hidden), t1.b#17(hidden), t1.b#18(hidden)], stream_key: [t1._row_id, t1.b, t1.a, t1.b#1, t1.b#2, t1.b#3, t1.b#4, t1.b#5, t1.b#6, t1.b#7, t1.b#8, t1.b#9, t1.b#10, t1.b#11, t1.b#12, t1.b#13, t1.b#14, t1.b#15, t1.b#16, t1.b#17, t1.b#18], pk_columns: [t1._row_id, t1.b, t1.a, t1.b#1, t1.b#2, t1.b#3, t1.b#4, t1.b#5, t1.b#6, t1.b#7, t1.b#8, t1.b#9, t1.b#10, t1.b#11, t1.b#12, t1.b#13, t1.b#14, t1.b#15, t1.b#16, t1.b#17, t1.b#18], pk_conflict: NoCheck } + └─StreamProject { exprs: [Coalesce(t1.b, 0:Int32) as $expr1, Coalesce(t1.b, 0:Int32) as $expr2, Coalesce(t1.b, 0:Int32) as $expr3, Coalesce(t1.b, 0:Int32) as $expr4, Coalesce(t1.b, 0:Int32) as $expr5, Coalesce(t1.b, 0:Int32) as $expr6, Coalesce(t1.b, 0:Int32) as $expr7, Coalesce(t1.b, 0:Int32) as $expr8, Coalesce(t1.b, 0:Int32) as $expr9, Coalesce(t1.b, 0:Int32) as $expr10, Coalesce(t1.b, 0:Int32) as $expr11, Coalesce(t1.b, 0:Int32) as $expr12, Coalesce(t1.b, 0:Int32) as $expr13, Coalesce(t1.b, 0:Int32) as $expr14, Coalesce(t1.b, 0:Int32) as $expr15, Coalesce(t1.b, 0:Int32) as $expr16, Coalesce(t1.b, 0:Int32) as $expr17, Coalesce(t1.b, 0:Int32) as $expr18, t1._row_id, t1.b, t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b] } + └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a, t1.b, t1.a] } + ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t1.b, t1._row_id, t1.a] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ └─StreamExchange { dist: HashShard(t2.c) } + │ └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + └─StreamProject { exprs: [t1.a, t1.b, t1.b] } + └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + ├─StreamExchange { dist: HashShard(t1.a) } + │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + └─StreamExchange { dist: HashShard(t2.c) } + └─StreamTableScan { table: t2, columns: [t2.c], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } +- name: improve multi scalar subqueries optimization time. issue 16952. case 2. + sql: | + create table t1(a int, b int); + create table t2(c int primary key, d int); + create table t3(e int, f int); + create table t4(g int, h int); + create table t5(i int, j int); + create table t6(k int, l int); + select + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col1, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col2, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col3, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col4, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col5, + COALESCE((SELECT sum(d) FROM t2 left join t3 on e = a and f = c left join t4 on g = a and h = c left join t5 on i = a and j = c WHERE t1.a = t2.c and t1.a = t2.c and j in (select k from t6 where b = l) ), 0) col6 + from t1; + batch_plan: |- + BatchExchange { order: [], dist: Single } + └─BatchProject { exprs: [Coalesce(sum(t2.d), 0:Int64) as $expr1, Coalesce(sum(t2.d), 0:Int64) as $expr2, Coalesce(sum(t2.d), 0:Int64) as $expr3, Coalesce(sum(t2.d), 0:Int64) as $expr4, Coalesce(sum(t2.d), 0:Int64) as $expr5, Coalesce(sum(t2.d), 0:Int64) as $expr6] } + └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d)] } + ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d)] } + │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d)] } + │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d), sum(t2.d)] } + │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d)] } + │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d)] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d)] } + │ │ │ │ │ └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ │ │ └─BatchHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j] } + │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: all, lookup table: t2 } + │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f] } + │ │ │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ │ │ │ └─BatchScan { table: t3, columns: [t3.e, t3.f], distribution: SomeShard } + │ │ │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h] } + │ │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t4.g, t4.h) } + │ │ │ │ │ │ │ └─BatchScan { table: t4, columns: [t4.g, t4.h], distribution: SomeShard } + │ │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j] } + │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t5.i, t5.j) } + │ │ │ │ │ │ └─BatchScan { table: t5, columns: [t5.i, t5.j], distribution: SomeShard } + │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t6.l) } + │ │ │ │ │ └─BatchScan { table: t6, columns: [t6.k, t6.l], distribution: SomeShard } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d)] } + │ │ │ │ └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ └─BatchHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ │ └─BatchHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j] } + │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: all, lookup table: t2 } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f] } + │ │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ │ │ └─BatchScan { table: t3, columns: [t3.e, t3.f], distribution: SomeShard } + │ │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h] } + │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t4.g, t4.h) } + │ │ │ │ │ │ └─BatchScan { table: t4, columns: [t4.g, t4.h], distribution: SomeShard } + │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t5.i, t5.j) } + │ │ │ │ │ └─BatchScan { table: t5, columns: [t5.i, t5.j], distribution: SomeShard } + │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k] } + │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t6.l) } + │ │ │ │ └─BatchScan { table: t6, columns: [t6.k, t6.l], distribution: SomeShard } + │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d)] } + │ │ │ └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ └─BatchHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ └─BatchHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j] } + │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: all, lookup table: t2 } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f] } + │ │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ │ └─BatchScan { table: t3, columns: [t3.e, t3.f], distribution: SomeShard } + │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t4.g, t4.h) } + │ │ │ │ │ └─BatchScan { table: t4, columns: [t4.g, t4.h], distribution: SomeShard } + │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j] } + │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t5.i, t5.j) } + │ │ │ │ └─BatchScan { table: t5, columns: [t5.i, t5.j], distribution: SomeShard } + │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k] } + │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t6.l) } + │ │ │ └─BatchScan { table: t6, columns: [t6.k, t6.l], distribution: SomeShard } + │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d)] } + │ │ └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ └─BatchHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ └─BatchHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j] } + │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: all, lookup table: t2 } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f] } + │ │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ └─BatchScan { table: t3, columns: [t3.e, t3.f], distribution: SomeShard } + │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h] } + │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t4.g, t4.h) } + │ │ │ │ └─BatchScan { table: t4, columns: [t4.g, t4.h], distribution: SomeShard } + │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j] } + │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t5.i, t5.j) } + │ │ │ └─BatchScan { table: t5, columns: [t5.i, t5.j], distribution: SomeShard } + │ │ └─BatchHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k] } + │ │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ └─BatchExchange { order: [], dist: HashShard(t6.l) } + │ │ └─BatchScan { table: t6, columns: [t6.k, t6.l], distribution: SomeShard } + │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d)] } + │ └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ └─BatchHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ └─BatchHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j] } + │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: all, lookup table: t2 } + │ │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f] } + │ │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t3.e, t3.f) } + │ │ │ │ └─BatchScan { table: t3, columns: [t3.e, t3.f], distribution: SomeShard } + │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h] } + │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t4.g, t4.h) } + │ │ │ └─BatchScan { table: t4, columns: [t4.g, t4.h], distribution: SomeShard } + │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j] } + │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ └─BatchExchange { order: [], dist: HashShard(t5.i, t5.j) } + │ │ └─BatchScan { table: t5, columns: [t5.i, t5.j], distribution: SomeShard } + │ └─BatchHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k] } + │ ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ └─BatchExchange { order: [], dist: HashShard(t6.l) } + │ └─BatchScan { table: t6, columns: [t6.k, t6.l], distribution: SomeShard } + └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d)] } + └─BatchHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + └─BatchHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d] } + ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ └─BatchHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j] } + │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ ├─BatchHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d] } + │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─BatchLookupJoin { type: Inner, predicate: t1.a = t2.c, output: all, lookup table: t2 } + │ │ │ │ └─BatchExchange { order: [], dist: UpstreamHashShard(t1.a) } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f] } + │ │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t3.e, t3.f) } + │ │ │ └─BatchScan { table: t3, columns: [t3.e, t3.f], distribution: SomeShard } + │ │ └─BatchHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h] } + │ │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ │ └─BatchExchange { order: [], dist: HashShard(t4.g, t4.h) } + │ │ └─BatchScan { table: t4, columns: [t4.g, t4.h], distribution: SomeShard } + │ └─BatchHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j] } + │ ├─BatchExchange { order: [], dist: HashShard(t1.a, t1.a) } + │ │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + │ └─BatchExchange { order: [], dist: HashShard(t5.i, t5.j) } + │ └─BatchScan { table: t5, columns: [t5.i, t5.j], distribution: SomeShard } + └─BatchHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k] } + ├─BatchExchange { order: [], dist: HashShard(t1.b) } + │ └─BatchHashAgg { group_key: [t1.a, t1.b], aggs: [] } + │ └─BatchExchange { order: [], dist: HashShard(t1.a, t1.b) } + │ └─BatchScan { table: t1, columns: [t1.a, t1.b], distribution: SomeShard } + └─BatchExchange { order: [], dist: HashShard(t6.l) } + └─BatchScan { table: t6, columns: [t6.k, t6.l], distribution: SomeShard } + stream_plan: |- + StreamMaterialize { columns: [col1, col2, col3, col4, col5, col6, t1._row_id(hidden), t1.a(hidden), t1.b(hidden)], stream_key: [t1._row_id, t1.a, t1.b], pk_columns: [t1._row_id, t1.a, t1.b], pk_conflict: NoCheck } + └─StreamProject { exprs: [Coalesce(sum(t2.d), 0:Int64) as $expr1, Coalesce(sum(t2.d), 0:Int64) as $expr2, Coalesce(sum(t2.d), 0:Int64) as $expr3, Coalesce(sum(t2.d), 0:Int64) as $expr4, Coalesce(sum(t2.d), 0:Int64) as $expr5, Coalesce(sum(t2.d), 0:Int64) as $expr6, t1._row_id, t1.a, t1.b] } + └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), t1._row_id, t1.a, t1.b, t1.a, t1.b] } + ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), t1._row_id, t1.a, t1.b] } + │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d), sum(t2.d), sum(t2.d), t1._row_id, t1.a, t1.b] } + │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d), sum(t2.d), t1._row_id, t1.a, t1.b] } + │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), sum(t2.d), t1._row_id, t1.a, t1.b] } + │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, sum(t2.d), t1._row_id, t1.a, t1.b] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, sum(t2.d)] } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d), count] } + │ │ │ │ │ └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t1.a, t1.b, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ │ │ └─StreamHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j, t3._row_id, t2.c, t4._row_id, t1.a, t1.b, t5._row_id] } + │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t3._row_id, t1.a, t1.b, t4._row_id] } + │ │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t1.a, t1.b, t3._row_id] } + │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c, t2.d], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f, t3._row_id] } + │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ │ │ │ └─StreamTableScan { table: t3, columns: [t3.e, t3.f, t3._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t3._row_id], pk: [_row_id], dist: UpstreamHashShard(t3._row_id) } + │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h, t4._row_id] } + │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t4.g, t4.h) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t4, columns: [t4.g, t4.h, t4._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t4._row_id], pk: [_row_id], dist: UpstreamHashShard(t4._row_id) } + │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j, t5._row_id] } + │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t5.i, t5.j) } + │ │ │ │ │ │ └─StreamTableScan { table: t5, columns: [t5.i, t5.j, t5._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t5._row_id], pk: [_row_id], dist: UpstreamHashShard(t5._row_id) } + │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k, t6._row_id] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t6.l) } + │ │ │ │ │ └─StreamTableScan { table: t6, columns: [t6.k, t6.l, t6._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t6._row_id], pk: [_row_id], dist: UpstreamHashShard(t6._row_id) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b, sum(t2.d)] } + │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d), count] } + │ │ │ │ └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t1.a, t1.b, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ └─StreamHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ │ └─StreamHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j, t3._row_id, t2.c, t4._row_id, t1.a, t1.b, t5._row_id] } + │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t3._row_id, t1.a, t1.b, t4._row_id] } + │ │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t1.a, t1.b, t3._row_id] } + │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c, t2.d], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f, t3._row_id] } + │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t3, columns: [t3.e, t3.f, t3._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t3._row_id], pk: [_row_id], dist: UpstreamHashShard(t3._row_id) } + │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h, t4._row_id] } + │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t4.g, t4.h) } + │ │ │ │ │ │ └─StreamTableScan { table: t4, columns: [t4.g, t4.h, t4._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t4._row_id], pk: [_row_id], dist: UpstreamHashShard(t4._row_id) } + │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j, t5._row_id] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t5.i, t5.j) } + │ │ │ │ │ └─StreamTableScan { table: t5, columns: [t5.i, t5.j, t5._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t5._row_id], pk: [_row_id], dist: UpstreamHashShard(t5._row_id) } + │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k, t6._row_id] } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ └─StreamExchange { dist: HashShard(t6.l) } + │ │ │ │ └─StreamTableScan { table: t6, columns: [t6.k, t6.l, t6._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t6._row_id], pk: [_row_id], dist: UpstreamHashShard(t6._row_id) } + │ │ │ └─StreamProject { exprs: [t1.a, t1.b, sum(t2.d)] } + │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d), count] } + │ │ │ └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t1.a, t1.b, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ └─StreamHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ └─StreamHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j, t3._row_id, t2.c, t4._row_id, t1.a, t1.b, t5._row_id] } + │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t3._row_id, t1.a, t1.b, t4._row_id] } + │ │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t1.a, t1.b, t3._row_id] } + │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c, t2.d], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f, t3._row_id] } + │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ │ └─StreamTableScan { table: t3, columns: [t3.e, t3.f, t3._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t3._row_id], pk: [_row_id], dist: UpstreamHashShard(t3._row_id) } + │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h, t4._row_id] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t4.g, t4.h) } + │ │ │ │ │ └─StreamTableScan { table: t4, columns: [t4.g, t4.h, t4._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t4._row_id], pk: [_row_id], dist: UpstreamHashShard(t4._row_id) } + │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j, t5._row_id] } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ └─StreamExchange { dist: HashShard(t5.i, t5.j) } + │ │ │ │ └─StreamTableScan { table: t5, columns: [t5.i, t5.j, t5._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t5._row_id], pk: [_row_id], dist: UpstreamHashShard(t5._row_id) } + │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k, t6._row_id] } + │ │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ └─StreamExchange { dist: HashShard(t6.l) } + │ │ │ └─StreamTableScan { table: t6, columns: [t6.k, t6.l, t6._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t6._row_id], pk: [_row_id], dist: UpstreamHashShard(t6._row_id) } + │ │ └─StreamProject { exprs: [t1.a, t1.b, sum(t2.d)] } + │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d), count] } + │ │ └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t1.a, t1.b, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ └─StreamHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ └─StreamHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j, t3._row_id, t2.c, t4._row_id, t1.a, t1.b, t5._row_id] } + │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t3._row_id, t1.a, t1.b, t4._row_id] } + │ │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t1.a, t1.b, t3._row_id] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c, t2.d], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f, t3._row_id] } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t3.e, t3.f) } + │ │ │ │ │ └─StreamTableScan { table: t3, columns: [t3.e, t3.f, t3._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t3._row_id], pk: [_row_id], dist: UpstreamHashShard(t3._row_id) } + │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h, t4._row_id] } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ └─StreamExchange { dist: HashShard(t4.g, t4.h) } + │ │ │ │ └─StreamTableScan { table: t4, columns: [t4.g, t4.h, t4._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t4._row_id], pk: [_row_id], dist: UpstreamHashShard(t4._row_id) } + │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j, t5._row_id] } + │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ └─StreamExchange { dist: HashShard(t5.i, t5.j) } + │ │ │ └─StreamTableScan { table: t5, columns: [t5.i, t5.j, t5._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t5._row_id], pk: [_row_id], dist: UpstreamHashShard(t5._row_id) } + │ │ └─StreamHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k, t6._row_id] } + │ │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ └─StreamExchange { dist: HashShard(t6.l) } + │ │ └─StreamTableScan { table: t6, columns: [t6.k, t6.l, t6._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t6._row_id], pk: [_row_id], dist: UpstreamHashShard(t6._row_id) } + │ └─StreamProject { exprs: [t1.a, t1.b, sum(t2.d)] } + │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d), count] } + │ └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t1.a, t1.b, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ └─StreamHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ └─StreamHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j, t3._row_id, t2.c, t4._row_id, t1.a, t1.b, t5._row_id] } + │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t3._row_id, t1.a, t1.b, t4._row_id] } + │ │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t1.a, t1.b, t3._row_id] } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c, t2.d], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f, t3._row_id] } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ └─StreamExchange { dist: HashShard(t3.e, t3.f) } + │ │ │ │ └─StreamTableScan { table: t3, columns: [t3.e, t3.f, t3._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t3._row_id], pk: [_row_id], dist: UpstreamHashShard(t3._row_id) } + │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h, t4._row_id] } + │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ └─StreamExchange { dist: HashShard(t4.g, t4.h) } + │ │ │ └─StreamTableScan { table: t4, columns: [t4.g, t4.h, t4._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t4._row_id], pk: [_row_id], dist: UpstreamHashShard(t4._row_id) } + │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j, t5._row_id] } + │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ └─StreamExchange { dist: HashShard(t5.i, t5.j) } + │ │ └─StreamTableScan { table: t5, columns: [t5.i, t5.j, t5._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t5._row_id], pk: [_row_id], dist: UpstreamHashShard(t5._row_id) } + │ └─StreamHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k, t6._row_id] } + │ ├─StreamExchange { dist: HashShard(t1.b) } + │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ └─StreamExchange { dist: HashShard(t6.l) } + │ └─StreamTableScan { table: t6, columns: [t6.k, t6.l, t6._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t6._row_id], pk: [_row_id], dist: UpstreamHashShard(t6._row_id) } + └─StreamProject { exprs: [t1.a, t1.b, sum(t2.d)] } + └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [sum(t2.d), count] } + └─StreamHashJoin { type: LeftOuter, predicate: t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t1.a, t1.b, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + ├─StreamExchange { dist: HashShard(t1.b) } + │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + └─StreamHashJoin { type: LeftSemi, predicate: t5.j = t6.k AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t3._row_id, t2.c, t4._row_id, t5._row_id, t5.j] } + ├─StreamExchange { dist: HashShard(t1.b) } + │ └─StreamHashJoin { type: LeftOuter, predicate: t2.c = t5.j AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.d, t5.j, t3._row_id, t2.c, t4._row_id, t1.a, t1.b, t5._row_id] } + │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t4.h AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t3._row_id, t1.a, t1.b, t4._row_id] } + │ │ ├─StreamHashJoin { type: LeftOuter, predicate: t2.c = t3.f AND t1.a IS NOT DISTINCT FROM t1.a AND t1.b IS NOT DISTINCT FROM t1.b, output: [t1.a, t1.b, t2.c, t2.d, t1.a, t1.b, t3._row_id] } + │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t2.c, output: all } + │ │ │ │ ├─StreamExchange { dist: HashShard(t1.a) } + │ │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ │ └─StreamExchange { dist: HashShard(t2.c) } + │ │ │ │ └─StreamTableScan { table: t2, columns: [t2.c, t2.d], stream_scan_type: ArrangementBackfill, stream_key: [t2.c], pk: [c], dist: UpstreamHashShard(t2.c) } + │ │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t3.e AND t1.a = t3.f, output: [t1.a, t1.b, t3.f, t3._row_id] } + │ │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ │ └─StreamExchange { dist: HashShard(t3.e, t3.f) } + │ │ │ └─StreamTableScan { table: t3, columns: [t3.e, t3.f, t3._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t3._row_id], pk: [_row_id], dist: UpstreamHashShard(t3._row_id) } + │ │ └─StreamHashJoin { type: Inner, predicate: t1.a = t4.g AND t1.a = t4.h, output: [t1.a, t1.b, t4.h, t4._row_id] } + │ │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ │ └─StreamExchange { dist: HashShard(t4.g, t4.h) } + │ │ └─StreamTableScan { table: t4, columns: [t4.g, t4.h, t4._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t4._row_id], pk: [_row_id], dist: UpstreamHashShard(t4._row_id) } + │ └─StreamHashJoin { type: Inner, predicate: t1.a = t5.i AND t1.a = t5.j, output: [t1.a, t1.b, t5.j, t5._row_id] } + │ ├─StreamExchange { dist: HashShard(t1.a, t1.a) } + │ │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + │ └─StreamExchange { dist: HashShard(t5.i, t5.j) } + │ └─StreamTableScan { table: t5, columns: [t5.i, t5.j, t5._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t5._row_id], pk: [_row_id], dist: UpstreamHashShard(t5._row_id) } + └─StreamHashJoin { type: Inner, predicate: t1.b = t6.l, output: [t1.a, t1.b, t6.k, t6._row_id] } + ├─StreamExchange { dist: HashShard(t1.b) } + │ └─StreamProject { exprs: [t1.a, t1.b], noop_update_hint: true } + │ └─StreamHashAgg { group_key: [t1.a, t1.b], aggs: [count] } + │ └─StreamExchange { dist: HashShard(t1.a, t1.b) } + │ └─StreamTableScan { table: t1, columns: [t1.a, t1.b, t1._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t1._row_id], pk: [_row_id], dist: UpstreamHashShard(t1._row_id) } + └─StreamExchange { dist: HashShard(t6.l) } + └─StreamTableScan { table: t6, columns: [t6.k, t6.l, t6._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t6._row_id], pk: [_row_id], dist: UpstreamHashShard(t6._row_id) } diff --git a/src/frontend/planner_test/tests/testdata/output/temporal_join.yaml b/src/frontend/planner_test/tests/testdata/output/temporal_join.yaml index aa8887e98bef6..5cdfdf6cf45ea 100644 --- a/src/frontend/planner_test/tests/testdata/output/temporal_join.yaml +++ b/src/frontend/planner_test/tests/testdata/output/temporal_join.yaml @@ -111,7 +111,7 @@ stream_plan: |- StreamMaterialize { columns: [k, x1, x2, a1, b1, stream._row_id(hidden), version2.k(hidden)], stream_key: [stream._row_id, k], pk_columns: [stream._row_id, k], pk_conflict: NoCheck } └─StreamExchange { dist: HashShard(stream.k, stream._row_id) } - └─StreamTemporalJoin { type: Inner, append_only: true, predicate: stream.k = version2.k, output: [stream.k, version1.x1, version2.x2, stream.a1, stream.b1, stream._row_id, version2.k] } + └─StreamTemporalJoin { type: Inner, append_only: false, predicate: stream.k = version2.k, output: [stream.k, version1.x1, version2.x2, stream.a1, stream.b1, stream._row_id, version2.k] } ├─StreamExchange { dist: HashShard(stream.k) } │ └─StreamTemporalJoin { type: Inner, append_only: false, predicate: stream.k = version1.k, output: [stream.k, stream.a1, stream.b1, version1.x1, stream._row_id, version1.k] } │ ├─StreamExchange { dist: HashShard(stream.k) } @@ -133,7 +133,7 @@ stream_plan: |- StreamMaterialize { columns: [id1, x1, id2, x2, a1, b1, stream._row_id(hidden), version2.id2(hidden)], stream_key: [stream._row_id, id1, id2], pk_columns: [stream._row_id, id1, id2], pk_conflict: NoCheck } └─StreamExchange { dist: HashShard(stream.id1, stream.id2, stream._row_id) } - └─StreamTemporalJoin { type: Inner, append_only: true, predicate: stream.id2 = version2.id2, output: [stream.id1, version1.x1, stream.id2, version2.x2, stream.a1, stream.b1, stream._row_id, version2.id2] } + └─StreamTemporalJoin { type: Inner, append_only: false, predicate: stream.id2 = version2.id2, output: [stream.id1, version1.x1, stream.id2, version2.x2, stream.a1, stream.b1, stream._row_id, version2.id2] } ├─StreamExchange { dist: HashShard(stream.id2) } │ └─StreamTemporalJoin { type: Inner, append_only: false, predicate: stream.id1 = version1.id1, output: [stream.id1, stream.id2, stream.a1, stream.b1, version1.x1, stream._row_id, version1.id1] } │ ├─StreamExchange { dist: HashShard(stream.id1) } @@ -155,7 +155,7 @@ stream_plan: |- StreamMaterialize { columns: [id1, x1, id2, x2, a1, b1, stream._row_id(hidden), version2.id2(hidden)], stream_key: [stream._row_id, id1, id2], pk_columns: [stream._row_id, id1, id2], pk_conflict: NoCheck } └─StreamExchange { dist: HashShard(stream.id1, stream.id2, stream._row_id) } - └─StreamTemporalJoin { type: Inner, append_only: true, predicate: stream.id2 = version2.id2, output: [stream.id1, version1.x1, stream.id2, version2.x2, stream.a1, stream.b1, stream._row_id, version2.id2] } + └─StreamTemporalJoin { type: Inner, append_only: false, predicate: stream.id2 = version2.id2, output: [stream.id1, version1.x1, stream.id2, version2.x2, stream.a1, stream.b1, stream._row_id, version2.id2] } ├─StreamExchange { dist: HashShard(stream.id2) } │ └─StreamTemporalJoin { type: Inner, append_only: false, predicate: stream.id1 = version1.id1, output: [stream.id1, stream.id2, stream.a1, stream.b1, version1.x1, stream._row_id, version1.id1] } │ ├─StreamExchange { dist: HashShard(stream.id1) } diff --git a/src/frontend/planner_test/tests/testdata/output/tpch.yaml b/src/frontend/planner_test/tests/testdata/output/tpch.yaml index dddddff210409..3c43faa8d2494 100644 --- a/src/frontend/planner_test/tests/testdata/output/tpch.yaml +++ b/src/frontend/planner_test/tests/testdata/output/tpch.yaml @@ -3650,14 +3650,14 @@ LogicalProject { exprs: [sum($expr1)] } └─LogicalAgg { aggs: [sum($expr1)] } └─LogicalProject { exprs: [(lineitem.l_extendedprice * (1:Int32::Decimal - lineitem.l_discount)) as $expr1] } - └─LogicalFilter { predicate: (part.p_partkey = lineitem.l_partkey) AND (part.p_size >= 1:Int32) AND In(lineitem.l_shipmode, 'AIR':Varchar, 'AIR REG':Varchar) AND (lineitem.l_shipinstruct = 'DELIVER IN PERSON':Varchar) AND (((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND (lineitem.l_quantity >= 1:Int32::Decimal)) AND (lineitem.l_quantity <= 11:Int32::Decimal)) AND (part.p_size <= 5:Int32)) OR (((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND (lineitem.l_quantity >= 30:Int32::Decimal)) AND (lineitem.l_quantity <= 40:Int32::Decimal)) AND (part.p_size <= 10:Int32))) OR (((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND (lineitem.l_quantity >= 10:Int32::Decimal)) AND (lineitem.l_quantity <= 20:Int32::Decimal)) AND (part.p_size <= 15:Int32))) } + └─LogicalFilter { predicate: (part.p_partkey = lineitem.l_partkey) AND (part.p_size >= 1:Int32) AND In(lineitem.l_shipmode, 'AIR':Varchar, 'AIR REG':Varchar) AND (lineitem.l_shipinstruct = 'DELIVER IN PERSON':Varchar) AND ((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND ((lineitem.l_quantity >= 1:Int32::Decimal) AND (lineitem.l_quantity <= 11:Int32::Decimal))) AND (part.p_size <= 5:Int32)) OR ((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND ((lineitem.l_quantity >= 30:Int32::Decimal) AND (lineitem.l_quantity <= 40:Int32::Decimal))) AND (part.p_size <= 10:Int32))) OR ((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND ((lineitem.l_quantity >= 10:Int32::Decimal) AND (lineitem.l_quantity <= 20:Int32::Decimal))) AND (part.p_size <= 15:Int32))) } └─LogicalJoin { type: Inner, on: true, output: all } ├─LogicalScan { table: lineitem, columns: [lineitem.l_orderkey, lineitem.l_partkey, lineitem.l_suppkey, lineitem.l_linenumber, lineitem.l_quantity, lineitem.l_extendedprice, lineitem.l_discount, lineitem.l_tax, lineitem.l_returnflag, lineitem.l_linestatus, lineitem.l_shipdate, lineitem.l_commitdate, lineitem.l_receiptdate, lineitem.l_shipinstruct, lineitem.l_shipmode, lineitem.l_comment] } └─LogicalScan { table: part, columns: [part.p_partkey, part.p_name, part.p_mfgr, part.p_brand, part.p_type, part.p_size, part.p_container, part.p_retailprice, part.p_comment] } optimized_logical_plan_for_batch: |- LogicalAgg { aggs: [sum($expr1)] } └─LogicalProject { exprs: [(lineitem.l_extendedprice * (1:Int32::Decimal - lineitem.l_discount)) as $expr1] } - └─LogicalJoin { type: Inner, on: (part.p_partkey = lineitem.l_partkey) AND (((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND (lineitem.l_quantity >= 1:Int32::Decimal)) AND (lineitem.l_quantity <= 11:Int32::Decimal)) AND (part.p_size <= 5:Int32)) OR (((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND (lineitem.l_quantity >= 30:Int32::Decimal)) AND (lineitem.l_quantity <= 40:Int32::Decimal)) AND (part.p_size <= 10:Int32))) OR (((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND (lineitem.l_quantity >= 10:Int32::Decimal)) AND (lineitem.l_quantity <= 20:Int32::Decimal)) AND (part.p_size <= 15:Int32))), output: [lineitem.l_extendedprice, lineitem.l_discount] } + └─LogicalJoin { type: Inner, on: (part.p_partkey = lineitem.l_partkey) AND ((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND ((lineitem.l_quantity >= 1:Int32::Decimal) AND (lineitem.l_quantity <= 11:Int32::Decimal))) AND (part.p_size <= 5:Int32)) OR ((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND ((lineitem.l_quantity >= 30:Int32::Decimal) AND (lineitem.l_quantity <= 40:Int32::Decimal))) AND (part.p_size <= 10:Int32))) OR ((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND ((lineitem.l_quantity >= 10:Int32::Decimal) AND (lineitem.l_quantity <= 20:Int32::Decimal))) AND (part.p_size <= 15:Int32))), output: [lineitem.l_extendedprice, lineitem.l_discount] } ├─LogicalScan { table: lineitem, output_columns: [lineitem.l_partkey, lineitem.l_quantity, lineitem.l_extendedprice, lineitem.l_discount], required_columns: [lineitem.l_partkey, lineitem.l_quantity, lineitem.l_extendedprice, lineitem.l_discount, lineitem.l_shipinstruct, lineitem.l_shipmode], predicate: In(lineitem.l_shipmode, 'AIR':Varchar, 'AIR REG':Varchar) AND (lineitem.l_shipinstruct = 'DELIVER IN PERSON':Varchar) } └─LogicalScan { table: part, columns: [part.p_partkey, part.p_brand, part.p_size, part.p_container], predicate: (part.p_size >= 1:Int32) } batch_plan: |- @@ -3665,7 +3665,7 @@ └─BatchExchange { order: [], dist: Single } └─BatchSimpleAgg { aggs: [sum($expr1)] } └─BatchProject { exprs: [(lineitem.l_extendedprice * (1:Decimal - lineitem.l_discount)) as $expr1] } - └─BatchLookupJoin { type: Inner, predicate: lineitem.l_partkey = part.p_partkey AND (((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND (lineitem.l_quantity >= 1:Decimal)) AND (lineitem.l_quantity <= 11:Decimal)) AND (part.p_size <= 5:Int32)) OR (((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND (lineitem.l_quantity >= 30:Decimal)) AND (lineitem.l_quantity <= 40:Decimal)) AND (part.p_size <= 10:Int32))) OR (((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND (lineitem.l_quantity >= 10:Decimal)) AND (lineitem.l_quantity <= 20:Decimal)) AND (part.p_size <= 15:Int32))) AND (part.p_size >= 1:Int32), output: [lineitem.l_extendedprice, lineitem.l_discount], lookup table: part } + └─BatchLookupJoin { type: Inner, predicate: lineitem.l_partkey = part.p_partkey AND ((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND ((lineitem.l_quantity >= 1:Decimal) AND (lineitem.l_quantity <= 11:Decimal))) AND (part.p_size <= 5:Int32)) OR ((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND ((lineitem.l_quantity >= 30:Decimal) AND (lineitem.l_quantity <= 40:Decimal))) AND (part.p_size <= 10:Int32))) OR ((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND ((lineitem.l_quantity >= 10:Decimal) AND (lineitem.l_quantity <= 20:Decimal))) AND (part.p_size <= 15:Int32))) AND (part.p_size >= 1:Int32), output: [lineitem.l_extendedprice, lineitem.l_discount], lookup table: part } └─BatchExchange { order: [], dist: UpstreamHashShard(lineitem.l_partkey) } └─BatchProject { exprs: [lineitem.l_partkey, lineitem.l_quantity, lineitem.l_extendedprice, lineitem.l_discount] } └─BatchFilter { predicate: In(lineitem.l_shipmode, 'AIR':Varchar, 'AIR REG':Varchar) AND (lineitem.l_shipinstruct = 'DELIVER IN PERSON':Varchar) } @@ -3677,7 +3677,7 @@ └─StreamExchange { dist: Single } └─StreamStatelessSimpleAgg { aggs: [sum($expr1)] } └─StreamProject { exprs: [(lineitem.l_extendedprice * (1:Decimal - lineitem.l_discount)) as $expr1, lineitem.l_orderkey, lineitem.l_linenumber, lineitem.l_partkey] } - └─StreamFilter { predicate: (((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND (lineitem.l_quantity >= 1:Decimal)) AND (lineitem.l_quantity <= 11:Decimal)) AND (part.p_size <= 5:Int32)) OR (((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND (lineitem.l_quantity >= 30:Decimal)) AND (lineitem.l_quantity <= 40:Decimal)) AND (part.p_size <= 10:Int32))) OR (((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND (lineitem.l_quantity >= 10:Decimal)) AND (lineitem.l_quantity <= 20:Decimal)) AND (part.p_size <= 15:Int32))) } + └─StreamFilter { predicate: ((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND ((lineitem.l_quantity >= 1:Decimal) AND (lineitem.l_quantity <= 11:Decimal))) AND (part.p_size <= 5:Int32)) OR ((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND ((lineitem.l_quantity >= 30:Decimal) AND (lineitem.l_quantity <= 40:Decimal))) AND (part.p_size <= 10:Int32))) OR ((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND ((lineitem.l_quantity >= 10:Decimal) AND (lineitem.l_quantity <= 20:Decimal))) AND (part.p_size <= 15:Int32))) } └─StreamHashJoin { type: Inner, predicate: lineitem.l_partkey = part.p_partkey, output: all } ├─StreamExchange { dist: HashShard(lineitem.l_partkey) } │ └─StreamProject { exprs: [lineitem.l_partkey, lineitem.l_quantity, lineitem.l_extendedprice, lineitem.l_discount, lineitem.l_orderkey, lineitem.l_linenumber] } @@ -3697,7 +3697,7 @@ Fragment 1 StreamStatelessSimpleAgg { aggs: [sum($expr1)] } └── StreamProject { exprs: [(lineitem.l_extendedprice * (1:Decimal - lineitem.l_discount)) as $expr1, lineitem.l_orderkey, lineitem.l_linenumber, lineitem.l_partkey] } - └── StreamFilter { predicate: (((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND (lineitem.l_quantity >= 1:Decimal)) AND (lineitem.l_quantity <= 11:Decimal)) AND (part.p_size <= 5:Int32)) OR (((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND (lineitem.l_quantity >= 30:Decimal)) AND (lineitem.l_quantity <= 40:Decimal)) AND (part.p_size <= 10:Int32))) OR (((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND (lineitem.l_quantity >= 10:Decimal)) AND (lineitem.l_quantity <= 20:Decimal)) AND (part.p_size <= 15:Int32))) } + └── StreamFilter { predicate: ((((((part.p_brand = 'Brand#52':Varchar) AND In(part.p_container, 'SM CASE':Varchar, 'SM BOX':Varchar, 'SM PACK':Varchar, 'SM PKG':Varchar)) AND ((lineitem.l_quantity >= 1:Decimal) AND (lineitem.l_quantity <= 11:Decimal))) AND (part.p_size <= 5:Int32)) OR ((((part.p_brand = 'Brand#24':Varchar) AND In(part.p_container, 'MED BAG':Varchar, 'MED BOX':Varchar, 'MED PKG':Varchar, 'MED PACK':Varchar)) AND ((lineitem.l_quantity >= 30:Decimal) AND (lineitem.l_quantity <= 40:Decimal))) AND (part.p_size <= 10:Int32))) OR ((((part.p_brand = 'Brand#32':Varchar) AND In(part.p_container, 'LG CASE':Varchar, 'LG BOX':Varchar, 'LG PACK':Varchar, 'LG PKG':Varchar)) AND ((lineitem.l_quantity >= 10:Decimal) AND (lineitem.l_quantity <= 20:Decimal))) AND (part.p_size <= 15:Int32))) } └── StreamHashJoin { type: Inner, predicate: lineitem.l_partkey = part.p_partkey, output: all } { tables: [ HashJoinLeft: 1, HashJoinDegreeLeft: 2, HashJoinRight: 3, HashJoinDegreeRight: 4 ] } ├── StreamExchange Hash([0]) from 2 └── StreamExchange Hash([0]) from 3 diff --git a/src/frontend/planner_test/tests/testdata/output/union.yaml b/src/frontend/planner_test/tests/testdata/output/union.yaml index c9591f2dee2d4..ffd31dec73da5 100644 --- a/src/frontend/planner_test/tests/testdata/output/union.yaml +++ b/src/frontend/planner_test/tests/testdata/output/union.yaml @@ -78,7 +78,7 @@ └─BatchScan { table: t2, columns: [t2.a, t2.b, t2.c], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } - └─StreamProject { exprs: [t1.a, t1.b, t1.c] } + └─StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └─StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } └─StreamExchange { dist: HashShard(t1.a, t1.b, t1.c) } └─StreamUnion { all: true } @@ -92,7 +92,7 @@ Fragment 0 StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } ├── tables: [ Materialize: 4294967294 ] - └── StreamProject { exprs: [t1.a, t1.b, t1.c] } + └── StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └── StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } { tables: [ HashAggState: 0 ] } └── StreamExchange Hash([0, 1, 2]) from 1 @@ -155,7 +155,7 @@ └─BatchScan { table: t2, columns: [t2.a, t2.b, t2.c], distribution: UpstreamHashShard(t2.a) } stream_plan: |- StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } - └─StreamProject { exprs: [t1.a, t1.b, t1.c] } + └─StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └─StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } └─StreamExchange { dist: HashShard(t1.a, t1.b, t1.c) } └─StreamUnion { all: true } @@ -169,7 +169,7 @@ Fragment 0 StreamMaterialize { columns: [a, b, c], stream_key: [a, b, c], pk_columns: [a, b, c], pk_conflict: NoCheck } ├── tables: [ Materialize: 4294967294 ] - └── StreamProject { exprs: [t1.a, t1.b, t1.c] } + └── StreamProject { exprs: [t1.a, t1.b, t1.c], noop_update_hint: true } └── StreamHashAgg { group_key: [t1.a, t1.b, t1.c], aggs: [count] } { tables: [ HashAggState: 0 ] } └── StreamExchange Hash([0, 1, 2]) from 1 diff --git a/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml b/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml index 8216619881a5a..1b811a4871993 100644 --- a/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml +++ b/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml @@ -30,7 +30,7 @@ ├─StreamExchange { dist: HashShard(t.arr) } │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } + └─StreamProject { exprs: [t.arr], noop_update_hint: true } └─StreamHashAgg { group_key: [t.arr], aggs: [count] } └─StreamExchange { dist: HashShard(t.arr) } └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -54,7 +54,7 @@ ├─StreamExchange { dist: HashShard(t.arr) } │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } + └─StreamProject { exprs: [t.arr], noop_update_hint: true } └─StreamHashAgg { group_key: [t.arr], aggs: [count] } └─StreamExchange { dist: HashShard(t.arr) } └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -78,7 +78,7 @@ ├─StreamExchange { dist: HashShard(t.arr) } │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } + └─StreamProject { exprs: [t.arr], noop_update_hint: true } └─StreamHashAgg { group_key: [t.arr], aggs: [count] } └─StreamExchange { dist: HashShard(t.arr) } └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -102,7 +102,7 @@ ├─StreamExchange { dist: HashShard(t.arr) } │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } + └─StreamProject { exprs: [t.arr], noop_update_hint: true } └─StreamHashAgg { group_key: [t.arr], aggs: [count] } └─StreamExchange { dist: HashShard(t.arr) } └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -127,7 +127,7 @@ ├─StreamExchange { dist: HashShard(t.arr) } │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } + └─StreamProject { exprs: [t.arr], noop_update_hint: true } └─StreamHashAgg { group_key: [t.arr], aggs: [count] } └─StreamExchange { dist: HashShard(t.arr) } └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } @@ -151,46 +151,34 @@ │ └─BatchScan { table: t, columns: [t.arr], distribution: SomeShard } └─BatchProjectSet { select_list: [$0, Unnest($0)] } └─BatchHashAgg { group_key: [t.arr], aggs: [] } - └─BatchHashJoin { type: Inner, predicate: t.arr IS NOT DISTINCT FROM t.arr, output: [t.arr] } - ├─BatchExchange { order: [], dist: HashShard(t.arr) } - │ └─BatchScan { table: t, columns: [t.arr], distribution: SomeShard } - └─BatchProjectSet { select_list: [$0, Unnest($0)] } - └─BatchHashAgg { group_key: [t.arr], aggs: [] } - └─BatchExchange { order: [], dist: HashShard(t.arr) } - └─BatchScan { table: t, columns: [t.arr], distribution: SomeShard } + └─BatchExchange { order: [], dist: HashShard(t.arr) } + └─BatchScan { table: t, columns: [t.arr], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [x, arr, unnest, ordinality, arr_2, ordinality_2, t._row_id(hidden), projected_row_id(hidden), projected_row_id#1(hidden)], stream_key: [t._row_id, projected_row_id, arr, projected_row_id#1], pk_columns: [t._row_id, projected_row_id, arr, projected_row_id#1], pk_conflict: NoCheck } - └─StreamProject { exprs: [t.x, t.arr, Unnest($0), $expr1, Unnest($0), (projected_row_id + 1:Int64) as $expr2, t._row_id, projected_row_id, projected_row_id] } - └─StreamHashJoin { type: Inner, predicate: t.arr IS NOT DISTINCT FROM t.arr, output: [t.x, t.arr, Unnest($0), $expr1, projected_row_id, t.arr, Unnest($0), t._row_id, projected_row_id] } - ├─StreamShare { id: 8 } - │ └─StreamProject { exprs: [t.x, t.arr, Unnest($0), (projected_row_id + 1:Int64) as $expr1, t._row_id, projected_row_id] } - │ └─StreamHashJoin { type: Inner, predicate: t.arr IS NOT DISTINCT FROM t.arr, output: [t.x, t.arr, projected_row_id, t.arr, Unnest($0), t._row_id] } - │ ├─StreamExchange { dist: HashShard(t.arr) } - │ │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - │ └─StreamProjectSet { select_list: [$0, Unnest($0)] } - │ └─StreamProject { exprs: [t.arr] } - │ └─StreamHashAgg { group_key: [t.arr], aggs: [count] } - │ └─StreamExchange { dist: HashShard(t.arr) } - │ └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } + └─StreamProject { exprs: [t.x, t.arr, Unnest($0), (projected_row_id + 1:Int64) as $expr1, Unnest($0), (projected_row_id + 1:Int64) as $expr2, t._row_id, projected_row_id, projected_row_id] } + └─StreamHashJoin { type: Inner, predicate: t.arr IS NOT DISTINCT FROM t.arr, output: [t.x, t.arr, projected_row_id, t.arr, Unnest($0), projected_row_id, t.arr, Unnest($0), t._row_id] } + ├─StreamHashJoin { type: Inner, predicate: t.arr IS NOT DISTINCT FROM t.arr, output: [t.x, t.arr, projected_row_id, t.arr, Unnest($0), t._row_id] } + │ ├─StreamExchange { dist: HashShard(t.arr) } + │ │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } + │ └─StreamProjectSet { select_list: [$0, Unnest($0)] } + │ └─StreamProject { exprs: [t.arr], noop_update_hint: true } + │ └─StreamHashAgg { group_key: [t.arr], aggs: [count] } + │ └─StreamExchange { dist: HashShard(t.arr) } + │ └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } + └─StreamProject { exprs: [t.arr], noop_update_hint: true } └─StreamHashAgg { group_key: [t.arr], aggs: [count] } - └─StreamShare { id: 8 } - └─StreamProject { exprs: [t.x, t.arr, Unnest($0), (projected_row_id + 1:Int64) as $expr1, t._row_id, projected_row_id] } - └─StreamHashJoin { type: Inner, predicate: t.arr IS NOT DISTINCT FROM t.arr, output: [t.x, t.arr, projected_row_id, t.arr, Unnest($0), t._row_id] } - ├─StreamExchange { dist: HashShard(t.arr) } - │ └─StreamTableScan { table: t, columns: [t.x, t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - └─StreamProjectSet { select_list: [$0, Unnest($0)] } - └─StreamProject { exprs: [t.arr] } - └─StreamHashAgg { group_key: [t.arr], aggs: [count] } - └─StreamExchange { dist: HashShard(t.arr) } - └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } + └─StreamExchange { dist: HashShard(t.arr) } + └─StreamTableScan { table: t, columns: [t.arr, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - sql: | select * from abs(1) WITH ORDINALITY; - batch_plan: 'BatchValues { rows: [[1:Int32, 1:Int64]] }' + batch_plan: |- + BatchProject { exprs: [, 1:Int64] } + └─BatchValues { rows: [[1:Int32]] } stream_plan: |- StreamMaterialize { columns: [abs, ordinality, _row_id(hidden)], stream_key: [_row_id], pk_columns: [_row_id], pk_conflict: NoCheck } - └─StreamValues { rows: [[1:Int32, 1:Int64, 0:Int64]] } + └─StreamProject { exprs: [, 1:Int64, _row_id] } + └─StreamValues { rows: [[1:Int32, 0:Int64]] } - sql: | create table t(x int , arr int[]); select * from t, abs(x) WITH ORDINALITY; @@ -210,4 +198,4 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ diff --git a/src/frontend/src/binder/bind_context.rs b/src/frontend/src/binder/bind_context.rs index 7a52de7decec4..688977f849916 100644 --- a/src/frontend/src/binder/bind_context.rs +++ b/src/frontend/src/binder/bind_context.rs @@ -23,6 +23,7 @@ use risingwave_common::catalog::{Field, Schema}; use risingwave_common::types::DataType; use risingwave_sqlparser::ast::TableAlias; +use crate::binder::Relation; use crate::error::{ErrorCode, Result}; type LiteResult = std::result::Result; @@ -81,12 +82,12 @@ pub struct LateralBindContext { /// WITH RECURSIVE t(n) AS ( /// # -------------^ => Init /// VALUES (1) -/// # ----------^ => BaseResolved (after binding the base term) /// UNION ALL -/// SELECT n+1 FROM t WHERE n < 100 -/// # ------------------^ => Bound (we know exactly what the entire cte looks like) +/// SELECT n + 1 FROM t WHERE n < 100 +/// # --------------------^ => BaseResolved (after binding the base term, this relation will be bound to `Relation::BackCteRef`) /// ) /// SELECT sum(n) FROM t; +/// # -----------------^ => Bound (we know exactly what the entire `RecursiveUnion` looks like, and this relation will be bound to `Relation::Share`) /// ``` #[derive(Default, Debug, Clone)] pub enum BindingCteState { @@ -94,11 +95,17 @@ pub enum BindingCteState { #[default] Init, /// We know the schema form after the base term resolved. - BaseResolved { schema: Schema }, + BaseResolved { + base: BoundSetExpr, + }, /// We get the whole bound result of the (recursive) CTE. Bound { query: Either, }, + + ChangeLog { + table: Relation, + }, } /// the entire `RecursiveUnion` represents a *bound* recursive cte. @@ -107,6 +114,7 @@ pub enum BindingCteState { pub struct RecursiveUnion { /// currently this *must* be true, /// otherwise binding will fail. + #[allow(dead_code)] pub all: bool, /// lhs part of the `UNION ALL` operator pub base: Box, diff --git a/src/frontend/src/binder/bind_param.rs b/src/frontend/src/binder/bind_param.rs index 6c3be04d4ee90..b4bbaf420e0c9 100644 --- a/src/frontend/src/binder/bind_param.rs +++ b/src/frontend/src/binder/bind_param.rs @@ -21,7 +21,7 @@ use risingwave_common::types::{Datum, ScalarImpl}; use super::statement::RewriteExprsRecursive; use super::BoundStatement; use crate::error::{ErrorCode, Result}; -use crate::expr::{Expr, ExprImpl, ExprRewriter, Literal}; +use crate::expr::{default_rewrite_expr, Expr, ExprImpl, ExprRewriter, Literal}; /// Rewrites parameter expressions to literals. pub(crate) struct ParamRewriter { @@ -47,22 +47,7 @@ impl ExprRewriter for ParamRewriter { if self.error.is_some() { return expr; } - match expr { - ExprImpl::InputRef(inner) => self.rewrite_input_ref(*inner), - ExprImpl::Literal(inner) => self.rewrite_literal(*inner), - ExprImpl::FunctionCall(inner) => self.rewrite_function_call(*inner), - ExprImpl::FunctionCallWithLambda(inner) => { - self.rewrite_function_call_with_lambda(*inner) - } - ExprImpl::AggCall(inner) => self.rewrite_agg_call(*inner), - ExprImpl::Subquery(inner) => self.rewrite_subquery(*inner), - ExprImpl::CorrelatedInputRef(inner) => self.rewrite_correlated_input_ref(*inner), - ExprImpl::TableFunction(inner) => self.rewrite_table_function(*inner), - ExprImpl::WindowFunction(inner) => self.rewrite_window_function(*inner), - ExprImpl::UserDefinedFunction(inner) => self.rewrite_user_defined_function(*inner), - ExprImpl::Parameter(inner) => self.rewrite_parameter(*inner), - ExprImpl::Now(inner) => self.rewrite_now(*inner), - } + default_rewrite_expr(self, expr) } fn rewrite_subquery(&mut self, mut subquery: crate::expr::Subquery) -> ExprImpl { diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index 2134e08fe8c66..95466021863e4 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -23,25 +23,26 @@ use risingwave_common::array::ListValue; use risingwave_common::catalog::{INFORMATION_SCHEMA_SCHEMA_NAME, PG_CATALOG_SCHEMA_NAME}; use risingwave_common::session_config::USER_NAME_WILD_CARD; use risingwave_common::types::{data_types, DataType, ScalarImpl, Timestamptz}; -use risingwave_common::{bail_not_implemented, current_cluster_version, no_function}; +use risingwave_common::{bail_not_implemented, current_cluster_version, must_match, no_function}; use risingwave_expr::aggregate::{agg_kinds, AggKind}; use risingwave_expr::window_function::{ Frame, FrameBound, FrameBounds, FrameExclusion, RangeFrameBounds, RangeFrameOffset, - RowsFrameBounds, WindowFuncKind, + RowsFrameBounds, SessionFrameBounds, SessionFrameGap, WindowFuncKind, }; use risingwave_sqlparser::ast::{ - self, Function, FunctionArg, FunctionArgExpr, Ident, WindowFrameBound, WindowFrameExclusion, - WindowFrameUnits, WindowSpec, + self, Function, FunctionArg, FunctionArgExpr, Ident, WindowFrameBound, WindowFrameBounds, + WindowFrameExclusion, WindowFrameUnits, WindowSpec, }; use risingwave_sqlparser::parser::ParserError; use thiserror_ext::AsReport; use crate::binder::bind_context::Clause; use crate::binder::{Binder, UdfContext}; +use crate::catalog::function_catalog::FunctionCatalog; use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{ - AggCall, Expr, ExprImpl, ExprType, FunctionCall, FunctionCallWithLambda, Literal, Now, OrderBy, - TableFunction, TableFunctionType, UserDefinedFunction, WindowFunction, + AggCall, CastContext, Expr, ExprImpl, ExprType, FunctionCall, FunctionCallWithLambda, Literal, + Now, OrderBy, TableFunction, TableFunctionType, UserDefinedFunction, WindowFunction, }; use crate::utils::Condition; @@ -95,21 +96,6 @@ impl Binder { _ => bail_not_implemented!(issue = 112, "qualified function {}", f.name), }; - // agg calls - if f.over.is_none() - && let Ok(kind) = function_name.parse() - { - return self.bind_agg(f, kind); - } - - if f.distinct || !f.order_by.is_empty() || f.filter.is_some() { - return Err(ErrorCode::InvalidInputSyntax(format!( - "DISTINCT, ORDER BY or FILTER is only allowed in aggregation functions, but `{}` is not an aggregation function", function_name - ) - ) - .into()); - } - // FIXME: This is a hack to support [Bytebase queries](https://github.com/TennyZhuang/bytebase/blob/4a26f7c62b80e86e58ad2f77063138dc2f420623/backend/plugin/db/pg/sync.go#L549). // Bytebase widely used the pattern like `obj_description(format('%s.%s', // quote_ident(idx.schemaname), quote_ident(idx.indexname))::regclass) AS comment` to @@ -119,47 +105,18 @@ impl Binder { if function_name == "obj_description" || function_name == "col_description" { return Ok(ExprImpl::literal_varchar("".to_string())); } - if function_name == "array_transform" { // For type inference, we need to bind the array type first. return self.bind_array_transform(f); } - // Used later in sql udf expression evaluation - let args = f.args.clone(); - - let mut inputs = f + let mut inputs: Vec<_> = f .args - .into_iter() - .map(|arg| self.bind_function_arg(arg)) + .iter() + .map(|arg| self.bind_function_arg(arg.clone())) .flatten_ok() .try_collect()?; - // window function - let window_func_kind = WindowFuncKind::from_str(function_name.as_str()); - if let Ok(kind) = window_func_kind { - if let Some(window_spec) = f.over { - return self.bind_window_function(kind, inputs, window_spec); - } - return Err(ErrorCode::InvalidInputSyntax(format!( - "Window function `{}` must have OVER clause", - function_name - )) - .into()); - } else if f.over.is_some() { - bail_not_implemented!( - issue = 8961, - "Unrecognized window function: {}", - function_name - ); - } - - // table function - if let Ok(function_type) = TableFunctionType::from_str(function_name.as_str()) { - self.ensure_table_function_allowed()?; - return Ok(TableFunction::new(function_type, inputs)?.into()); - } - // user defined function // TODO: resolve schema name https://github.com/risingwavelabs/risingwave/issues/12422 if let Ok(schema) = self.first_valid_schema() @@ -200,7 +157,7 @@ impl Binder { // The actual inline logic for sql udf // Note that we will always create new udf context for each sql udf - let Ok(context) = UdfContext::create_udf_context(&args, &Arc::clone(func)) else { + let Ok(context) = UdfContext::create_udf_context(&f.args, &Arc::clone(func)) else { return Err(ErrorCode::InvalidInputSyntax( "failed to create the `udf_context`, please recheck your function definition and syntax".to_string() ) @@ -268,11 +225,53 @@ impl Binder { self.ensure_table_function_allowed()?; return Ok(TableFunction::new_user_defined(func.clone(), inputs).into()); } - Aggregate => todo!("support UDAF"), + Aggregate => { + return self.bind_agg(f, AggKind::UserDefined, Some(func.clone())); + } } } } + // agg calls + if f.over.is_none() + && let Ok(kind) = function_name.parse() + { + return self.bind_agg(f, kind, None); + } + + if f.distinct || !f.order_by.is_empty() || f.filter.is_some() { + return Err(ErrorCode::InvalidInputSyntax(format!( + "DISTINCT, ORDER BY or FILTER is only allowed in aggregation functions, but `{}` is not an aggregation function", function_name + ) + ) + .into()); + } + + // window function + let window_func_kind = WindowFuncKind::from_str(function_name.as_str()); + if let Ok(kind) = window_func_kind { + if let Some(window_spec) = f.over { + return self.bind_window_function(kind, inputs, window_spec); + } + return Err(ErrorCode::InvalidInputSyntax(format!( + "Window function `{}` must have OVER clause", + function_name + )) + .into()); + } else if f.over.is_some() { + bail_not_implemented!( + issue = 8961, + "Unrecognized window function: {}", + function_name + ); + } + + // table function + if let Ok(function_type) = TableFunctionType::from_str(function_name.as_str()) { + self.ensure_table_function_allowed()?; + return Ok(TableFunction::new(function_type, inputs)?.into()); + } + self.bind_builtin_scalar_function(function_name.as_str(), inputs, f.variadic) } @@ -351,7 +350,12 @@ impl Binder { Ok(body) } - pub(super) fn bind_agg(&mut self, f: Function, kind: AggKind) -> Result { + pub(super) fn bind_agg( + &mut self, + f: Function, + kind: AggKind, + user_defined: Option>, + ) -> Result { self.ensure_aggregate_allowed()?; let distinct = f.distinct; @@ -385,14 +389,26 @@ impl Binder { None => Condition::true_cond(), }; - Ok(ExprImpl::AggCall(Box::new(AggCall::new( - kind, - args, - distinct, - order_by, - filter, - direct_args, - )?))) + if let Some(user_defined) = user_defined { + Ok(AggCall::new_user_defined( + args, + distinct, + order_by, + filter, + direct_args, + user_defined, + )? + .into()) + } else { + Ok(ExprImpl::AggCall(Box::new(AggCall::new( + kind, + args, + distinct, + order_by, + filter, + direct_args, + )?))) + } } fn bind_ordered_set_agg( @@ -614,34 +630,35 @@ impl Binder { }; let bounds = match frame.units { WindowFrameUnits::Rows => { - let (start, end) = - self.bind_window_frame_usize_bounds(frame.start_bound, frame.end_bound)?; + let (start, end) = must_match!(frame.bounds, WindowFrameBounds::Bounds { start, end } => (start, end)); + let (start, end) = self.bind_window_frame_usize_bounds(start, end)?; FrameBounds::Rows(RowsFrameBounds { start, end }) } - WindowFrameUnits::Range => { + unit @ (WindowFrameUnits::Range | WindowFrameUnits::Session) => { let order_by_expr = order_by .sort_exprs .iter() - // for `RANGE` frame, there should be exactly one `ORDER BY` column + // for `RANGE | SESSION` frame, there should be exactly one `ORDER BY` column .exactly_one() .map_err(|_| { - ErrorCode::InvalidInputSyntax( - "there should be exactly one ordering column for `RANGE` frame" - .to_string(), - ) + ErrorCode::InvalidInputSyntax(format!( + "there should be exactly one ordering column for `{}` frame", + unit + )) })?; let order_data_type = order_by_expr.expr.return_type(); let order_type = order_by_expr.order_type; let offset_data_type = match &order_data_type { - // for numeric ordering columns, `offset` should be the same type + // for numeric ordering columns, `offset`/`gap` should be the same type // NOTE: actually in PG it can be a larger type, but we don't support this here t @ data_types::range_frame_numeric!() => t.clone(), - // for datetime ordering columns, `offset` should be interval + // for datetime ordering columns, `offset`/`gap` should be interval t @ data_types::range_frame_datetime!() => { if matches!(t, DataType::Date | DataType::Time) { bail_not_implemented!( - "`RANGE` frame with offset of type `{}` is not implemented yet, please manually cast the `ORDER BY` column to `timestamp`", + "`{}` frame with offset of type `{}` is not implemented yet, please manually cast the `ORDER BY` column to `timestamp`", + unit, t ); } @@ -651,8 +668,8 @@ impl Binder { t => { return Err(ErrorCode::NotSupported( format!( - "`RANGE` frame with offset of type `{}` is not supported", - t + "`{}` frame with offset of type `{}` is not supported", + unit, t ), "Please re-consider the `ORDER BY` column".to_string(), ) @@ -660,18 +677,31 @@ impl Binder { } }; - let (start, end) = self.bind_window_frame_scalar_impl_bounds( - frame.start_bound, - frame.end_bound, - &offset_data_type, - )?; - FrameBounds::Range(RangeFrameBounds { - order_data_type, - order_type, - offset_data_type, - start: start.map(RangeFrameOffset::new), - end: end.map(RangeFrameOffset::new), - }) + if unit == WindowFrameUnits::Range { + let (start, end) = must_match!(frame.bounds, WindowFrameBounds::Bounds { start, end } => (start, end)); + let (start, end) = self.bind_window_frame_scalar_impl_bounds( + start, + end, + &offset_data_type, + )?; + FrameBounds::Range(RangeFrameBounds { + order_data_type, + order_type, + offset_data_type, + start: start.map(RangeFrameOffset::new), + end: end.map(RangeFrameOffset::new), + }) + } else { + let gap = must_match!(frame.bounds, WindowFrameBounds::Gap(gap) => gap); + let gap_value = + self.bind_window_frame_bound_offset(*gap, offset_data_type.clone())?; + FrameBounds::Session(SessionFrameBounds { + order_data_type, + order_type, + gap_data_type: offset_data_type, + gap: SessionFrameGap::new(gap_value), + }) + } } WindowFrameUnits::Groups => { bail_not_implemented!( @@ -766,13 +796,13 @@ impl Binder { let mut offset = self.bind_expr(offset)?; if !offset.is_const() { return Err(ErrorCode::InvalidInputSyntax( - "offset in window frame bounds must be constant".to_string(), + "offset/gap in window frame bounds must be constant".to_string(), ) .into()); } if offset.cast_implicit_mut(cast_to.clone()).is_err() { return Err(ErrorCode::InvalidInputSyntax(format!( - "offset in window frame bounds must be castable to {}", + "offset/gap in window frame bounds must be castable to {}", cast_to )) .into()); @@ -780,7 +810,7 @@ impl Binder { let offset = offset.fold_const()?; let Some(offset) = offset else { return Err(ErrorCode::InvalidInputSyntax( - "offset in window frame bounds must not be NULL".to_string(), + "offset/gap in window frame bounds must not be NULL".to_string(), ) .into()); }; @@ -1012,6 +1042,22 @@ impl Binder { ("to_ascii", raw_call(ExprType::ToAscii)), ("to_hex", raw_call(ExprType::ToHex)), ("quote_ident", raw_call(ExprType::QuoteIdent)), + ("quote_literal", guard_by_len(1, raw(|_binder, mut inputs| { + if inputs[0].return_type() != DataType::Varchar { + // Support `quote_literal(any)` by converting it to `quote_literal(any::text)` + // Ref. https://github.com/postgres/postgres/blob/REL_16_1/src/include/catalog/pg_proc.dat#L4641 + FunctionCall::cast_mut(&mut inputs[0], DataType::Varchar, CastContext::Explicit)?; + } + Ok(FunctionCall::new_unchecked(ExprType::QuoteLiteral, inputs, DataType::Varchar).into()) + }))), + ("quote_nullable", guard_by_len(1, raw(|_binder, mut inputs| { + if inputs[0].return_type() != DataType::Varchar { + // Support `quote_nullable(any)` by converting it to `quote_nullable(any::text)` + // Ref. https://github.com/postgres/postgres/blob/REL_16_1/src/include/catalog/pg_proc.dat#L4650 + FunctionCall::cast_mut(&mut inputs[0], DataType::Varchar, CastContext::Explicit)?; + } + Ok(FunctionCall::new_unchecked(ExprType::QuoteNullable, inputs, DataType::Varchar).into()) + }))), ("string_to_array", raw_call(ExprType::StringToArray)), ("encode", raw_call(ExprType::Encode)), ("decode", raw_call(ExprType::Decode)), @@ -1128,6 +1174,7 @@ impl Binder { ("jsonb_path_exists", raw_call(ExprType::JsonbPathExists)), ("jsonb_path_query_array", raw_call(ExprType::JsonbPathQueryArray)), ("jsonb_path_query_first", raw_call(ExprType::JsonbPathQueryFirst)), + ("jsonb_set", raw_call(ExprType::JsonbSet)), // Functions that return a constant value ("pi", pi()), // greatest and least @@ -1209,6 +1256,7 @@ impl Binder { ("pg_get_userbyid", raw_call(ExprType::PgGetUserbyid)), ("pg_get_indexdef", raw_call(ExprType::PgGetIndexdef)), ("pg_get_viewdef", raw_call(ExprType::PgGetViewdef)), + ("pg_index_column_has_property", raw_call(ExprType::PgIndexColumnHasProperty)), ("pg_relation_size", raw(|_binder, mut inputs|{ if inputs.is_empty() { return Err(ErrorCode::ExprError( @@ -1303,6 +1351,51 @@ impl Binder { ("pg_get_partkeydef", raw_literal(ExprImpl::literal_null(DataType::Varchar))), ("pg_encoding_to_char", raw_literal(ExprImpl::literal_varchar("UTF8".into()))), ("has_database_privilege", raw_literal(ExprImpl::literal_bool(true))), + ("has_table_privilege", raw(|binder, mut inputs|{ + if inputs.len() == 2 { + inputs.insert(0, ExprImpl::literal_varchar(binder.auth_context.user_name.clone())); + } + if inputs.len() == 3 { + if inputs[1].return_type() == DataType::Varchar { + inputs[1].cast_to_regclass_mut()?; + } + Ok(FunctionCall::new(ExprType::HasTablePrivilege, inputs)?.into()) + } else { + Err(ErrorCode::ExprError( + "Too many/few arguments for pg_catalog.has_table_privilege()".into(), + ) + .into()) + } + })), + ("has_any_column_privilege", raw(|binder, mut inputs|{ + if inputs.len() == 2 { + inputs.insert(0, ExprImpl::literal_varchar(binder.auth_context.user_name.clone())); + } + if inputs.len() == 3 { + if inputs[1].return_type() == DataType::Varchar { + inputs[1].cast_to_regclass_mut()?; + } + Ok(FunctionCall::new(ExprType::HasAnyColumnPrivilege, inputs)?.into()) + } else { + Err(ErrorCode::ExprError( + "Too many/few arguments for pg_catalog.has_any_column_privilege()".into(), + ) + .into()) + } + })), + ("has_schema_privilege", raw(|binder, mut inputs|{ + if inputs.len() == 2 { + inputs.insert(0, ExprImpl::literal_varchar(binder.auth_context.user_name.clone())); + } + if inputs.len() == 3 { + Ok(FunctionCall::new(ExprType::HasSchemaPrivilege, inputs)?.into()) + } else { + Err(ErrorCode::ExprError( + "Too many/few arguments for pg_catalog.has_schema_privilege()".into(), + ) + .into()) + } + })), ("pg_stat_get_numscans", raw_literal(ExprImpl::literal_bigint(0))), ("pg_backend_pid", raw(|binder, _inputs| { // FIXME: the session id is not global unique in multi-frontend env. @@ -1333,6 +1426,7 @@ impl Binder { ("pg_is_in_recovery", raw_literal(ExprImpl::literal_bool(false))), // internal ("rw_vnode", raw_call(ExprType::Vnode)), + ("rw_test_paid_tier", raw_call(ExprType::TestPaidTier)), // for testing purposes // TODO: choose which pg version we should return. ("version", raw_literal(ExprImpl::literal_varchar(current_cluster_version()))), // non-deterministic @@ -1479,11 +1573,15 @@ impl Binder { if self.is_for_stream() && !matches!( self.context.clause, - Some(Clause::Where) | Some(Clause::Having) | Some(Clause::JoinOn) + Some(Clause::Where) + | Some(Clause::Having) + | Some(Clause::JoinOn) + | Some(Clause::From) ) { return Err(ErrorCode::InvalidInputSyntax(format!( - "For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING` and `ON`. Found in clause: {:?}. Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information", + "For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING`, `ON` and `FROM`. Found in clause: {:?}. \ + Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information", self.context.clause )) .into()); diff --git a/src/frontend/src/binder/expr/mod.rs b/src/frontend/src/binder/expr/mod.rs index 0b8b50be0eab6..363e6f0738fef 100644 --- a/src/frontend/src/binder/expr/mod.rs +++ b/src/frontend/src/binder/expr/mod.rs @@ -175,7 +175,7 @@ impl Binder { Expr::AtTimeZone { timestamp, time_zone, - } => self.bind_at_time_zone(*timestamp, time_zone), + } => self.bind_at_time_zone(*timestamp, *time_zone), // special syntax for string Expr::Trim { expr, @@ -219,9 +219,9 @@ impl Binder { .into()) } - pub(super) fn bind_at_time_zone(&mut self, input: Expr, time_zone: String) -> Result { + pub(super) fn bind_at_time_zone(&mut self, input: Expr, time_zone: Expr) -> Result { let input = self.bind_expr_inner(input)?; - let time_zone = self.bind_string(time_zone)?.into(); + let time_zone = self.bind_expr_inner(time_zone)?; FunctionCall::new(ExprType::AtTimeZone, vec![input, time_zone]).map(Into::into) } diff --git a/src/frontend/src/binder/expr/value.rs b/src/frontend/src/binder/expr/value.rs index 0b6eace27083f..a0758a15d4440 100644 --- a/src/frontend/src/binder/expr/value.rs +++ b/src/frontend/src/binder/expr/value.rs @@ -221,13 +221,12 @@ impl Binder { #[cfg(test)] mod tests { use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::DataType; use risingwave_expr::expr::build_from_prost; use risingwave_sqlparser::ast::Value::Number; use super::*; use crate::binder::test_utils::mock_binder; - use crate::expr::{Expr, ExprImpl, ExprType, FunctionCall}; + use crate::expr::Expr; #[tokio::test] async fn test_bind_value() { diff --git a/src/frontend/src/binder/insert.rs b/src/frontend/src/binder/insert.rs index 1cc719d066420..e0b5ce422e75a 100644 --- a/src/frontend/src/binder/insert.rs +++ b/src/frontend/src/binder/insert.rs @@ -175,14 +175,14 @@ impl Binder { // is given and it is NOT equivalent to assignment cast over potential implicit cast inside. // For example, the following is valid: // - // ``` + // ```sql // create table t (v1 time); // insert into t values (timestamp '2020-01-01 01:02:03'), (time '03:04:05'); // ``` // // But the followings are not: // - // ``` + // ```sql // values (timestamp '2020-01-01 01:02:03'), (time '03:04:05'); // insert into t values (timestamp '2020-01-01 01:02:03'), (time '03:04:05') limit 1; // ``` diff --git a/src/frontend/src/binder/mod.rs b/src/frontend/src/binder/mod.rs index 3648f53d50271..8b526a78d53f4 100644 --- a/src/frontend/src/binder/mod.rs +++ b/src/frontend/src/binder/mod.rs @@ -49,8 +49,9 @@ pub use insert::BoundInsert; use pgwire::pg_server::{Session, SessionId}; pub use query::BoundQuery; pub use relation::{ - BoundBaseTable, BoundJoin, BoundShare, BoundSource, BoundSystemTable, BoundWatermark, - BoundWindowTableFunction, Relation, ResolveQualifiedNameError, WindowTableFunctionKind, + BoundBackCteRef, BoundBaseTable, BoundJoin, BoundShare, BoundShareInput, BoundSource, + BoundSystemTable, BoundWatermark, BoundWindowTableFunction, Relation, + ResolveQualifiedNameError, WindowTableFunctionKind, }; pub use select::{BoundDistinct, BoundSelect}; pub use set_expr::*; @@ -260,17 +261,17 @@ impl UdfContext { /// following the rules: /// 1. At the beginning, it contains the user specified parameters type. /// 2. When the binder encounters a parameter, it will record it as unknown(call `record_new_param`) -/// if it didn't exist in `ParameterTypes`. +/// if it didn't exist in `ParameterTypes`. /// 3. When the binder encounters a cast on parameter, if it's a unknown type, the cast function -/// will record the target type as infer type for that parameter(call `record_infer_type`). If the -/// parameter has been inferred, the cast function will act as a normal cast. +/// will record the target type as infer type for that parameter(call `record_infer_type`). If the +/// parameter has been inferred, the cast function will act as a normal cast. /// 4. After bind finished: /// (a) parameter not in `ParameterTypes` means that the user didn't specify it and it didn't -/// occur in the query. `export` will return error if there is a kind of -/// parameter. This rule is compatible with PostgreSQL +/// occur in the query. `export` will return error if there is a kind of +/// parameter. This rule is compatible with PostgreSQL /// (b) parameter is None means that it's a unknown type. The user didn't specify it -/// and we can't infer it in the query. We will treat it as VARCHAR type finally. This rule is -/// compatible with PostgreSQL. +/// and we can't infer it in the query. We will treat it as VARCHAR type finally. This rule is +/// compatible with PostgreSQL. /// (c) parameter is Some means that it's a known type. #[derive(Clone, Debug)] pub struct ParameterTypes(Arc>>>); @@ -573,130 +574,167 @@ mod tests { Share( BoundShare { share_id: 0, - input: Right( - RecursiveUnion { - all: true, - base: Select( - BoundSelect { - distinct: All, - select_items: [ - Literal( - Literal { - data: Some( - Int32( - 1, + input: Query( + Right( + RecursiveUnion { + all: true, + base: Select( + BoundSelect { + distinct: All, + select_items: [ + Literal( + Literal { + data: Some( + Int32( + 1, + ), ), - ), - data_type: Some( - Int32, - ), - }, - ), - ], - aliases: [ - Some( - "a", - ), - ], - from: None, - where_clause: None, - group_by: GroupKey( - [], - ), - having: None, - schema: Schema { - fields: [ - a:Int32, + data_type: Some( + Int32, + ), + }, + ), + ], + aliases: [ + Some( + "a", + ), ], + from: None, + where_clause: None, + group_by: GroupKey( + [], + ), + having: None, + schema: Schema { + fields: [ + a:Int32, + ], + }, }, - }, - ), - recursive: Select( - BoundSelect { - distinct: All, - select_items: [ - FunctionCall( - FunctionCall { - func_type: Add, - return_type: Int32, - inputs: [ - InputRef( - InputRef { - index: 0, - data_type: Int32, - }, - ), - Literal( - Literal { - data: Some( - Int32( - 1, + ), + recursive: Select( + BoundSelect { + distinct: All, + select_items: [ + FunctionCall( + FunctionCall { + func_type: Add, + return_type: Int32, + inputs: [ + InputRef( + InputRef { + index: 0, + data_type: Int32, + }, + ), + Literal( + Literal { + data: Some( + Int32( + 1, + ), ), + data_type: Some( + Int32, + ), + }, + ), + ], + }, + ), + ], + aliases: [ + None, + ], + from: Some( + BackCteRef( + BoundBackCteRef { + share_id: 0, + base: Select( + BoundSelect { + distinct: All, + select_items: [ + Literal( + Literal { + data: Some( + Int32( + 1, + ), + ), + data_type: Some( + Int32, + ), + }, + ), + ], + aliases: [ + Some( + "a", + ), + ], + from: None, + where_clause: None, + group_by: GroupKey( + [], ), - data_type: Some( - Int32, - ), + having: None, + schema: Schema { + fields: [ + a:Int32, + ], + }, }, ), - ], - }, - ), - ], - aliases: [ - None, - ], - from: Some( - BackCteRef( - BoundBackCteRef { - share_id: 0, - }, + }, + ), ), - ), - where_clause: Some( - FunctionCall( - FunctionCall { - func_type: LessThan, - return_type: Boolean, - inputs: [ - InputRef( - InputRef { - index: 0, - data_type: Int32, - }, - ), - Literal( - Literal { - data: Some( - Int32( - 10, + where_clause: Some( + FunctionCall( + FunctionCall { + func_type: LessThan, + return_type: Boolean, + inputs: [ + InputRef( + InputRef { + index: 0, + data_type: Int32, + }, + ), + Literal( + Literal { + data: Some( + Int32( + 10, + ), ), - ), - data_type: Some( - Int32, - ), - }, - ), - ], - }, + data_type: Some( + Int32, + ), + }, + ), + ], + }, + ), ), - ), - group_by: GroupKey( - [], - ), - having: None, - schema: Schema { - fields: [ - ?column?:Int32, - ], + group_by: GroupKey( + [], + ), + having: None, + schema: Schema { + fields: [ + ?column?:Int32, + ], + }, }, + ), + schema: Schema { + fields: [ + a:Int32, + ], }, - ), - schema: Schema { - fields: [ - a:Int32, - ], }, - }, + ), ), }, ), diff --git a/src/frontend/src/binder/query.rs b/src/frontend/src/binder/query.rs index 39de526662976..459e1b7921e94 100644 --- a/src/frontend/src/binder/query.rs +++ b/src/frontend/src/binder/query.rs @@ -20,7 +20,7 @@ use risingwave_common::catalog::Schema; use risingwave_common::types::DataType; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_sqlparser::ast::{ - Cte, Expr, Fetch, OrderByExpr, Query, SetExpr, SetOperator, Value, With, + Cte, CteInner, Expr, Fetch, ObjectName, OrderByExpr, Query, SetExpr, SetOperator, Value, With, }; use thiserror_ext::AsReport; @@ -284,123 +284,203 @@ impl Binder { fn bind_with(&mut self, with: With) -> Result<()> { for cte_table in with.cte_tables { + // note that the new `share_id` for the rcte is generated here let share_id = self.next_share_id(); - let Cte { alias, query, .. } = cte_table; + let Cte { alias, cte_inner } = cte_table; let table_name = alias.name.real_value(); if with.recursive { - let Query { - with, - body, - order_by, - limit, - offset, - fetch, - } = query; - - /// the input clause should not be supported. - fn should_be_empty(v: Option, clause: &str) -> Result<()> { - if v.is_some() { - return Err(ErrorCode::BindError(format!( - "`{clause}` is not supported in recursive CTE" - )) + if let CteInner::Query(query) = cte_inner { + let ( + SetExpr::SetOperation { + op: SetOperator::Union, + all, + left, + right, + }, + with, + ) = Self::validate_rcte(query)? + else { + return Err(ErrorCode::BindError( + "expect `SetOperation` as the return type of validation".into(), + ) .into()); - } - Ok(()) - } - - should_be_empty(order_by.first(), "ORDER BY")?; - should_be_empty(limit, "LIMIT")?; - should_be_empty(offset, "OFFSET")?; - should_be_empty(fetch, "FETCH")?; - - let SetExpr::SetOperation { - op: SetOperator::Union, - all, - left, - right, - } = body - else { + }; + + let entry = self + .context + .cte_to_relation + .entry(table_name) + .insert_entry(Rc::new(RefCell::new(BindingCte { + share_id, + state: BindingCteState::Init, + alias, + }))) + .get() + .clone(); + + self.bind_rcte(with, entry, *left, *right, all)?; + } else { return Err(ErrorCode::BindError( - "`UNION` is required in recursive CTE".to_string(), - ) - .into()); - }; - - if !all { - return Err(ErrorCode::BindError( - "only `UNION ALL` is supported in recursive CTE now".to_string(), + "RECURSIVE CTE only support query".to_string(), ) .into()); } - - let entry = self - .context - .cte_to_relation - .entry(table_name) - .insert_entry(Rc::new(RefCell::new(BindingCte { - share_id, - state: BindingCteState::Init, - alias, - }))) - .get() - .clone(); - - self.push_context(); - if let Some(with) = with { - self.bind_with(with)?; + } else { + match cte_inner { + CteInner::Query(query) => { + let bound_query = self.bind_query(query)?; + self.context.cte_to_relation.insert( + table_name, + Rc::new(RefCell::new(BindingCte { + share_id, + state: BindingCteState::Bound { + query: either::Either::Left(bound_query), + }, + alias, + })), + ); + } + CteInner::ChangeLog(from_table_name) => { + self.push_context(); + let from_table_relation = self.bind_relation_by_name( + ObjectName::from(vec![from_table_name]), + None, + None, + )?; + self.pop_context()?; + self.context.cte_to_relation.insert( + table_name, + Rc::new(RefCell::new(BindingCte { + share_id, + state: BindingCteState::ChangeLog { + table: from_table_relation, + }, + alias, + })), + ); + } } + } + } + Ok(()) + } - // We assume `left` is the base term, otherwise the implementation may be very hard. - // The behavior is the same as PostgreSQL's. - // reference: - let mut base = self.bind_set_expr(*left)?; - - entry.borrow_mut().state = BindingCteState::BaseResolved { - schema: base.schema().clone(), - }; - - // Reset context for right side, but keep `cte_to_relation`. - let new_context = std::mem::take(&mut self.context); - self.context - .cte_to_relation - .clone_from(&new_context.cte_to_relation); - // bind the rest of the recursive cte - let mut recursive = self.bind_set_expr(*right)?; - // Reset context for the set operation. - self.context = Default::default(); - self.context.cte_to_relation = new_context.cte_to_relation; - - Self::align_schema(&mut base, &mut recursive, SetOperator::Union)?; - let schema = base.schema().clone(); - - let recursive_union = RecursiveUnion { - all, - base: Box::new(base), - recursive: Box::new(recursive), - schema, - }; - - entry.borrow_mut().state = BindingCteState::Bound { - query: either::Either::Right(recursive_union), - }; - // TODO: This does not execute during early return by `?` - // We shall extract it similar to `bind_query` and `bind_query_inner`. - self.pop_context()?; - } else { - let bound_query = self.bind_query(query)?; - self.context.cte_to_relation.insert( - table_name, - Rc::new(RefCell::new(BindingCte { - share_id, - state: BindingCteState::Bound { - query: either::Either::Left(bound_query), - }, - alias, - })), - ); + /// syntactically validate the recursive cte ast with the current support features in rw. + fn validate_rcte(query: Query) -> Result<(SetExpr, Option)> { + let Query { + with, + body, + order_by, + limit, + offset, + fetch, + } = query; + + /// the input clause should not be supported. + fn should_be_empty(v: Option, clause: &str) -> Result<()> { + if v.is_some() { + return Err(ErrorCode::BindError(format!( + "`{clause}` is not supported in recursive CTE" + )) + .into()); } + Ok(()) } + + should_be_empty(order_by.first(), "ORDER BY")?; + should_be_empty(limit, "LIMIT")?; + should_be_empty(offset, "OFFSET")?; + should_be_empty(fetch, "FETCH")?; + + let SetExpr::SetOperation { + op: SetOperator::Union, + all, + left, + right, + } = body + else { + return Err( + ErrorCode::BindError("`UNION` is required in recursive CTE".to_string()).into(), + ); + }; + + if !all { + return Err(ErrorCode::BindError( + "only `UNION ALL` is supported in recursive CTE now".to_string(), + ) + .into()); + } + + Ok(( + SetExpr::SetOperation { + op: SetOperator::Union, + all, + left, + right, + }, + with, + )) + } + + fn bind_rcte( + &mut self, + with: Option, + entry: Rc>, + left: SetExpr, + right: SetExpr, + all: bool, + ) -> Result<()> { + self.push_context(); + let result = self.bind_rcte_inner(with, entry, left, right, all); + self.pop_context()?; + result + } + + fn bind_rcte_inner( + &mut self, + with: Option, + entry: Rc>, + left: SetExpr, + right: SetExpr, + all: bool, + ) -> Result<()> { + if let Some(with) = with { + self.bind_with(with)?; + } + + // We assume `left` is the base term, otherwise the implementation may be very hard. + // The behavior is the same as PostgreSQL's. + // reference: + let mut base = self.bind_set_expr(left)?; + + entry.borrow_mut().state = BindingCteState::BaseResolved { base: base.clone() }; + + // Reset context for right side, but keep `cte_to_relation`. + let new_context = std::mem::take(&mut self.context); + self.context + .cte_to_relation + .clone_from(&new_context.cte_to_relation); + // bind the rest of the recursive cte + let mut recursive = self.bind_set_expr(right)?; + // Reset context for the set operation. + self.context = Default::default(); + self.context.cte_to_relation = new_context.cte_to_relation; + + Self::align_schema(&mut base, &mut recursive, SetOperator::Union)?; + let schema = base.schema().clone(); + + let recursive_union = RecursiveUnion { + all, + base: Box::new(base), + recursive: Box::new(recursive), + schema, + }; + + entry.borrow_mut().state = BindingCteState::Bound { + query: either::Either::Right(recursive_union), + }; + Ok(()) } } diff --git a/src/frontend/src/binder/relation/cte_ref.rs b/src/frontend/src/binder/relation/cte_ref.rs index 86c40cc096dd1..87e87fb1823ae 100644 --- a/src/frontend/src/binder/relation/cte_ref.rs +++ b/src/frontend/src/binder/relation/cte_ref.rs @@ -13,14 +13,14 @@ // limitations under the License. use crate::binder::statement::RewriteExprsRecursive; -use crate::binder::ShareId; +use crate::binder::{BoundSetExpr, ShareId}; /// A CTE reference, currently only used in the back reference of recursive CTE. /// For the non-recursive one, see [`BoundShare`](super::BoundShare). #[derive(Debug, Clone)] pub struct BoundBackCteRef { - #[expect(dead_code)] pub(crate) share_id: ShareId, + pub(crate) base: BoundSetExpr, } impl RewriteExprsRecursive for BoundBackCteRef { diff --git a/src/frontend/src/binder/relation/join.rs b/src/frontend/src/binder/relation/join.rs index c2f820aff7744..d13b683be08b0 100644 --- a/src/frontend/src/binder/relation/join.rs +++ b/src/frontend/src/binder/relation/join.rs @@ -164,10 +164,10 @@ impl Binder { JoinConstraint::Using(cols) => { // sanity check for col in &cols { - if old_context.indices_of.get(&col.real_value()).is_none() { + if !old_context.indices_of.contains_key(&col.real_value()) { return Err(ErrorCode::ItemNotFound(format!("column \"{}\" specified in USING clause does not exist in left table", col.real_value())).into()); } - if self.context.indices_of.get(&col.real_value()).is_none() { + if !self.context.indices_of.contains_key(&col.real_value()) { return Err(ErrorCode::ItemNotFound(format!("column \"{}\" specified in USING clause does not exist in right table", col.real_value())).into()); } } diff --git a/src/frontend/src/binder/relation/mod.rs b/src/frontend/src/binder/relation/mod.rs index b7c288551191c..2cdf3ea07db4e 100644 --- a/src/frontend/src/binder/relation/mod.rs +++ b/src/frontend/src/binder/relation/mod.rs @@ -15,7 +15,6 @@ use std::collections::hash_map::Entry; use std::ops::Deref; -use either::Either; use itertools::{EitherOrBoth, Itertools}; use risingwave_common::bail; use risingwave_common::catalog::{Field, TableId, DEFAULT_SCHEMA_NAME}; @@ -26,7 +25,6 @@ use risingwave_sqlparser::ast::{ use thiserror::Error; use thiserror_ext::AsReport; -use self::cte_ref::BoundBackCteRef; use super::bind_context::ColumnBinding; use super::statement::RewriteExprsRecursive; use crate::binder::bind_context::{BindingCte, BindingCteState}; @@ -43,8 +41,9 @@ mod table_or_source; mod watermark; mod window_table_function; +pub use cte_ref::BoundBackCteRef; pub use join::BoundJoin; -pub use share::BoundShare; +pub use share::{BoundShare, BoundShareInput}; pub use subquery::BoundSubquery; pub use table_or_source::{BoundBaseTable, BoundSource, BoundSystemTable}; pub use watermark::BoundWatermark; @@ -374,29 +373,34 @@ impl Binder { BindingCteState::Init => { Err(ErrorCode::BindError("Base term of recursive CTE not found, consider writing it to left side of the `UNION ALL` operator".to_string()).into()) } - BindingCteState::BaseResolved { schema } => { + BindingCteState::BaseResolved { base } => { self.bind_table_to_context( - schema.fields.iter().map(|f| (false, f.clone())), + base.schema().fields.iter().map(|f| (false, f.clone())), table_name.clone(), Some(original_alias), )?; - Ok(Relation::BackCteRef(Box::new(BoundBackCteRef { share_id }))) + Ok(Relation::BackCteRef(Box::new(BoundBackCteRef { share_id, base }))) } BindingCteState::Bound { query } => { - let schema = match &query { - Either::Left(normal) => normal.schema(), - Either::Right(recursive) => &recursive.schema, - }; + let input = BoundShareInput::Query(query); self.bind_table_to_context( - schema.fields.iter().map(|f| (false, f.clone())), + input.fields()?, table_name.clone(), Some(original_alias), )?; // we could always share the cte, // no matter it's recursive or not. - let input = query; - Ok(Relation::Share(Box::new(BoundShare { share_id, input }))) + Ok(Relation::Share(Box::new(BoundShare { share_id, input}))) } + BindingCteState::ChangeLog { table } => { + let input = BoundShareInput::ChangeLog(table); + self.bind_table_to_context( + input.fields()?, + table_name.clone(), + Some(original_alias), + )?; + Ok(Relation::Share(Box::new(BoundShare { share_id, input }))) + }, } } else { self.bind_relation_by_name_inner(schema_name.as_deref(), &table_name, alias, as_of) diff --git a/src/frontend/src/binder/relation/share.rs b/src/frontend/src/binder/relation/share.rs index 3a64189bd8861..8b8afbadeb527 100644 --- a/src/frontend/src/binder/relation/share.rs +++ b/src/frontend/src/binder/relation/share.rs @@ -13,24 +13,98 @@ // limitations under the License. use either::Either; +use itertools::Itertools; +use risingwave_common::catalog::Field; use crate::binder::bind_context::RecursiveUnion; use crate::binder::statement::RewriteExprsRecursive; -use crate::binder::{BoundQuery, ShareId}; +use crate::binder::{BoundQuery, Relation, ShareId}; +use crate::error::{ErrorCode, Result}; +use crate::optimizer::plan_node::generic::{CHANGELOG_OP, _CHANGELOG_ROW_ID}; /// Share a relation during binding and planning. /// It could be used to share a (recursive) CTE, a source, a view and so on. + +#[derive(Debug, Clone)] +pub enum BoundShareInput { + Query(Either), + ChangeLog(Relation), +} +impl BoundShareInput { + pub fn fields(&self) -> Result> { + match self { + BoundShareInput::Query(q) => match q { + Either::Left(q) => Ok(q + .schema() + .fields() + .iter() + .cloned() + .map(|f| (false, f)) + .collect_vec()), + Either::Right(r) => Ok(r + .schema + .fields() + .iter() + .cloned() + .map(|f| (false, f)) + .collect_vec()), + }, + BoundShareInput::ChangeLog(r) => { + let (fields, _name) = if let Relation::BaseTable(bound_base_table) = r { + ( + bound_base_table.table_catalog.columns().to_vec(), + bound_base_table.table_catalog.name().to_string(), + ) + } else { + return Err(ErrorCode::BindError( + "Change log CTE must be a base table".to_string(), + ) + .into()); + }; + let fields = fields + .into_iter() + .map(|x| { + ( + x.is_hidden, + Field::with_name(x.data_type().clone(), x.name()), + ) + }) + .chain(vec![ + ( + false, + Field::with_name( + risingwave_common::types::DataType::Int16, + CHANGELOG_OP.to_string(), + ), + ), + ( + true, + Field::with_name( + risingwave_common::types::DataType::Serial, + _CHANGELOG_ROW_ID.to_string(), + ), + ), + ]) + .collect(); + Ok(fields) + } + } + } +} #[derive(Debug, Clone)] pub struct BoundShare { pub(crate) share_id: ShareId, - pub(crate) input: Either, + pub(crate) input: BoundShareInput, } impl RewriteExprsRecursive for BoundShare { fn rewrite_exprs_recursive(&mut self, rewriter: &mut impl crate::expr::ExprRewriter) { match &mut self.input { - Either::Left(q) => q.rewrite_exprs_recursive(rewriter), - Either::Right(r) => r.rewrite_exprs_recursive(rewriter), + BoundShareInput::Query(q) => match q { + Either::Left(q) => q.rewrite_exprs_recursive(rewriter), + Either::Right(r) => r.rewrite_exprs_recursive(rewriter), + }, + BoundShareInput::ChangeLog(r) => r.rewrite_exprs_recursive(rewriter), }; } } diff --git a/src/frontend/src/binder/relation/table_function.rs b/src/frontend/src/binder/relation/table_function.rs index 9189e176e0d55..1a609f87670f3 100644 --- a/src/frontend/src/binder/relation/table_function.rs +++ b/src/frontend/src/binder/relation/table_function.rs @@ -16,18 +16,13 @@ use std::str::FromStr; use itertools::Itertools; use risingwave_common::bail_not_implemented; -use risingwave_common::catalog::{ - Field, Schema, PG_CATALOG_SCHEMA_NAME, RW_INTERNAL_TABLE_FUNCTION_NAME, -}; +use risingwave_common::catalog::{Field, Schema, RW_INTERNAL_TABLE_FUNCTION_NAME}; use risingwave_common::types::DataType; use risingwave_sqlparser::ast::{Function, FunctionArg, ObjectName, TableAlias}; use super::watermark::is_watermark_func; use super::{Binder, Relation, Result, WindowTableFunctionKind}; use crate::binder::bind_context::Clause; -use crate::catalog::system_catalog::pg_catalog::{ - PG_GET_KEYWORDS_FUNC_NAME, PG_KEYWORDS_TABLE_NAME, -}; use crate::error::ErrorCode; use crate::expr::{Expr, ExprImpl}; @@ -57,24 +52,6 @@ impl Binder { } return self.bind_internal_table(args, alias); } - if func_name.eq_ignore_ascii_case(PG_GET_KEYWORDS_FUNC_NAME) - || name.real_value().eq_ignore_ascii_case( - format!("{}.{}", PG_CATALOG_SCHEMA_NAME, PG_GET_KEYWORDS_FUNC_NAME).as_str(), - ) - { - if with_ordinality { - bail_not_implemented!( - "WITH ORDINALITY for internal/system table function {}", - func_name - ); - } - return self.bind_relation_by_name_inner( - Some(PG_CATALOG_SCHEMA_NAME), - PG_KEYWORDS_TABLE_NAME, - alias, - None, - ); - } } // window table functions (tumble/hop) if let Ok(kind) = WindowTableFunctionKind::from_str(func_name) { @@ -143,7 +120,7 @@ impl Binder { // Note: named return value should take precedence over table alias. // But we don't support it yet. // e.g., - // ``` + // ```sql // > create function foo(ret out int) language sql as 'select 1'; // > select t.ret from foo() as t; // ``` diff --git a/src/frontend/src/binder/relation/table_or_source.rs b/src/frontend/src/binder/relation/table_or_source.rs index c5283a2cc592a..c0ff372581b17 100644 --- a/src/frontend/src/binder/relation/table_or_source.rs +++ b/src/frontend/src/binder/relation/table_or_source.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use either::Either; use itertools::Itertools; use risingwave_common::bail_not_implemented; -use risingwave_common::catalog::{is_system_schema, Field}; +use risingwave_common::catalog::{debug_assert_column_ids_distinct, is_system_schema, Field}; use risingwave_common::session_config::USER_NAME_WILD_CARD; use risingwave_connector::WithPropertiesExt; use risingwave_sqlparser::ast::{AsOf, Statement, TableAlias}; @@ -25,6 +25,7 @@ use risingwave_sqlparser::parser::Parser; use thiserror_ext::AsReport; use super::BoundShare; +use crate::binder::relation::BoundShareInput; use crate::binder::{Binder, Relation}; use crate::catalog::root_catalog::SchemaPath; use crate::catalog::source_catalog::SourceCatalog; @@ -119,9 +120,9 @@ impl Binder { table_name ); } - } else if let Ok((table_catalog, schema_name)) = - self.catalog - .get_table_by_name(&self.db_name, schema_path, table_name) + } else if let Ok((table_catalog, schema_name)) = self + .catalog + .get_created_table_by_name(&self.db_name, schema_path, table_name) { self.resolve_table_relation(table_catalog.clone(), schema_name, as_of)? } else if let Ok((source_catalog, _)) = @@ -162,7 +163,9 @@ impl Binder { if let Ok(schema) = self.catalog.get_schema_by_name(&self.db_name, schema_name) { - if let Some(table_catalog) = schema.get_table_by_name(table_name) { + if let Some(table_catalog) = + schema.get_created_table_by_name(table_name) + { return self.resolve_table_relation( table_catalog.clone(), &schema_name.clone(), @@ -221,6 +224,7 @@ impl Binder { source_catalog: &SourceCatalog, as_of: Option, ) -> (Relation, Vec<(bool, Field)>) { + debug_assert_column_ids_distinct(&source_catalog.columns); self.included_relations.insert(source_catalog.id.into()); ( Relation::Source(Box::new(BoundSource { @@ -280,7 +284,10 @@ impl Binder { }; let input = Either::Left(query); Ok(( - Relation::Share(Box::new(BoundShare { share_id, input })), + Relation::Share(Box::new(BoundShare { + share_id, + input: BoundShareInput::Query(input), + })), columns.iter().map(|c| (false, c.clone())).collect_vec(), )) } @@ -309,7 +316,7 @@ impl Binder { }; let (table_catalog, schema_name) = self.catalog - .get_table_by_name(db_name, schema_path, table_name)?; + .get_created_table_by_name(db_name, schema_path, table_name)?; let table_catalog = table_catalog.clone(); let table_id = table_catalog.id(); @@ -347,7 +354,7 @@ impl Binder { let (table, _schema_name) = self.catalog - .get_table_by_name(db_name, schema_path, table_name)?; + .get_created_table_by_name(db_name, schema_path, table_name)?; match table.table_type() { TableType::Table => {} diff --git a/src/frontend/src/binder/select.rs b/src/frontend/src/binder/select.rs index d9848ed769732..e15a9eac73246 100644 --- a/src/frontend/src/binder/select.rs +++ b/src/frontend/src/binder/select.rs @@ -25,11 +25,12 @@ use risingwave_sqlparser::ast::{ use super::bind_context::{Clause, ColumnBinding}; use super::statement::RewriteExprsRecursive; -use super::UNNAMED_COLUMN; +use super::{BoundShareInput, UNNAMED_COLUMN}; use crate::binder::{Binder, Relation}; use crate::catalog::check_valid_column_name; use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{CorrelatedId, Depth, Expr as _, ExprImpl, ExprType, FunctionCall, InputRef}; +use crate::optimizer::plan_node::generic::CHANGELOG_OP; use crate::utils::group_by::GroupBy; #[derive(Debug, Clone)] @@ -282,6 +283,17 @@ impl Binder { }) .collect::>>()?; + if let Some(Relation::Share(bound)) = &from { + if matches!(bound.input, BoundShareInput::ChangeLog(_)) + && fields.iter().filter(|&x| x.name.eq(CHANGELOG_OP)).count() > 1 + { + return Err(ErrorCode::BindError( + "The source table of changelog cannot have `changelog_op`, please rename it first".to_string() + ) + .into()); + } + } + Ok(BoundSelect { distinct, select_items, diff --git a/src/frontend/src/binder/values.rs b/src/frontend/src/binder/values.rs index 93f9e3f4f8a18..9e190c3cf4dc5 100644 --- a/src/frontend/src/binder/values.rs +++ b/src/frontend/src/binder/values.rs @@ -154,7 +154,7 @@ impl Binder { #[cfg(test)] mod tests { - use risingwave_common::util::iter_util::{zip_eq_fast, ZipEqFast}; + use risingwave_common::util::iter_util::zip_eq_fast; use risingwave_sqlparser::ast::{Expr, Value}; use super::*; diff --git a/src/frontend/src/catalog/catalog_service.rs b/src/frontend/src/catalog/catalog_service.rs index 7994fca811a35..5f42d1e73e5bb 100644 --- a/src/frontend/src/catalog/catalog_service.rs +++ b/src/frontend/src/catalog/catalog_service.rs @@ -26,7 +26,7 @@ use risingwave_pb::catalog::{ use risingwave_pb::ddl_service::alter_owner_request::Object; use risingwave_pb::ddl_service::{ alter_name_request, alter_set_schema_request, create_connection_request, PbReplaceTablePlan, - PbTableJobType, ReplaceTablePlan, + PbTableJobType, ReplaceTablePlan, TableJobType, }; use risingwave_pb::meta::PbTableParallelism; use risingwave_pb::stream_plan::StreamFragmentGraph; @@ -34,7 +34,7 @@ use risingwave_rpc_client::MetaClient; use tokio::sync::watch::Receiver; use super::root_catalog::Catalog; -use super::{DatabaseId, TableId}; +use super::{DatabaseId, SecretId, TableId}; use crate::error::Result; use crate::user::UserId; @@ -43,6 +43,7 @@ pub type CatalogReadGuard = ArcRwLockReadGuard; /// [`CatalogReader`] can read catalog from local catalog and force the holder can not modify it. #[derive(Clone)] pub struct CatalogReader(Arc>); + impl CatalogReader { pub fn new(inner: Arc>) -> Self { CatalogReader(inner) @@ -91,6 +92,7 @@ pub trait CatalogWriter: Send + Sync { table: PbTable, graph: StreamFragmentGraph, mapping: ColIndexMapping, + job_type: TableJobType, ) -> Result<()>; async fn alter_source_column(&self, source: PbSource) -> Result<()>; @@ -117,11 +119,7 @@ pub trait CatalogWriter: Send + Sync { affected_table_change: Option, ) -> Result<()>; - async fn create_subscription( - &self, - subscription: PbSubscription, - graph: StreamFragmentGraph, - ) -> Result<()>; + async fn create_subscription(&self, subscription: PbSubscription) -> Result<()>; async fn create_function(&self, function: PbFunction) -> Result<()>; @@ -134,6 +132,15 @@ pub trait CatalogWriter: Send + Sync { connection: create_connection_request::Payload, ) -> Result<()>; + async fn create_secret( + &self, + secret_name: String, + database_id: u32, + schema_id: u32, + owner_id: u32, + payload: Vec, + ) -> Result<()>; + async fn comment_on(&self, comment: PbComment) -> Result<()>; async fn drop_table( @@ -168,6 +175,8 @@ pub trait CatalogWriter: Send + Sync { async fn drop_connection(&self, connection_id: u32) -> Result<()>; + async fn drop_secret(&self, secret_id: SecretId) -> Result<()>; + async fn alter_table_name(&self, table_id: u32, table_name: &str) -> Result<()>; async fn alter_view_name(&self, view_id: u32, view_name: &str) -> Result<()>; @@ -301,10 +310,11 @@ impl CatalogWriter for CatalogWriterImpl { table: PbTable, graph: StreamFragmentGraph, mapping: ColIndexMapping, + job_type: TableJobType, ) -> Result<()> { let version = self .meta_client - .replace_table(source, table, graph, mapping) + .replace_table(source, table, graph, mapping, job_type) .await?; self.wait_version(version).await } @@ -339,15 +349,8 @@ impl CatalogWriter for CatalogWriterImpl { self.wait_version(version).await } - async fn create_subscription( - &self, - subscription: PbSubscription, - graph: StreamFragmentGraph, - ) -> Result<()> { - let version = self - .meta_client - .create_subscription(subscription, graph) - .await?; + async fn create_subscription(&self, subscription: PbSubscription) -> Result<()> { + let version = self.meta_client.create_subscription(subscription).await?; self.wait_version(version).await } @@ -377,6 +380,21 @@ impl CatalogWriter for CatalogWriterImpl { self.wait_version(version).await } + async fn create_secret( + &self, + secret_name: String, + database_id: u32, + schema_id: u32, + owner_id: u32, + payload: Vec, + ) -> Result<()> { + let version = self + .meta_client + .create_secret(secret_name, database_id, schema_id, owner_id, payload) + .await?; + self.wait_version(version).await + } + async fn comment_on(&self, comment: PbComment) -> Result<()> { let version = self.meta_client.comment_on(comment).await?; self.wait_version(version).await @@ -459,6 +477,11 @@ impl CatalogWriter for CatalogWriterImpl { self.wait_version(version).await } + async fn drop_secret(&self, secret_id: SecretId) -> Result<()> { + let version = self.meta_client.drop_secret(secret_id).await?; + self.wait_version(version).await + } + async fn alter_table_name(&self, table_id: u32, table_name: &str) -> Result<()> { let version = self .meta_client diff --git a/src/frontend/src/catalog/database_catalog.rs b/src/frontend/src/catalog/database_catalog.rs index ec040e309eba8..cb4a35236baff 100644 --- a/src/frontend/src/catalog/database_catalog.rs +++ b/src/frontend/src/catalog/database_catalog.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use itertools::Itertools; use risingwave_common::catalog::PG_CATALOG_SCHEMA_NAME; use risingwave_pb::catalog::{PbDatabase, PbSchema}; +use risingwave_pb::user::grant_privilege::Object; use super::OwnedByUserCatalog; use crate::catalog::schema_catalog::SchemaCatalog; @@ -96,7 +97,17 @@ impl DatabaseCatalog { pub fn find_schema_containing_table_id(&self, table_id: &TableId) -> Option<&SchemaCatalog> { self.schema_by_name .values() - .find(|schema| schema.get_table_by_id(table_id).is_some()) + .find(|schema| schema.get_created_table_by_id(table_id).is_some()) + } + + pub fn get_grant_object_by_oid(&self, oid: u32) -> Option { + for schema in self.schema_by_name.values() { + let object = schema.get_grant_object_by_oid(oid); + if object.is_some() { + return object; + } + } + None } pub fn update_schema(&mut self, prost: &PbSchema) { diff --git a/src/frontend/src/catalog/function_catalog.rs b/src/frontend/src/catalog/function_catalog.rs index 1a0e01db1b92a..e042f33f377a8 100644 --- a/src/frontend/src/catalog/function_catalog.rs +++ b/src/frontend/src/catalog/function_catalog.rs @@ -12,11 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use enum_as_inner::EnumAsInner; use parse_display::Display; use risingwave_common::catalog::FunctionId; use risingwave_common::types::DataType; use risingwave_pb::catalog::function::PbKind; use risingwave_pb::catalog::PbFunction; +use risingwave_pb::expr::PbUserDefinedFunctionMetadata; use crate::catalog::OwnedByUserCatalog; @@ -39,7 +41,7 @@ pub struct FunctionCatalog { pub runtime: Option, } -#[derive(Clone, Display, PartialEq, Eq, Hash, Debug)] +#[derive(Clone, Display, PartialEq, Eq, Hash, Debug, EnumAsInner)] #[display(style = "UPPERCASE")] pub enum FunctionKind { Scalar, @@ -80,6 +82,22 @@ impl From<&PbFunction> for FunctionCatalog { } } +impl From<&FunctionCatalog> for PbUserDefinedFunctionMetadata { + fn from(c: &FunctionCatalog) -> Self { + PbUserDefinedFunctionMetadata { + arg_names: c.arg_names.clone(), + arg_types: c.arg_types.iter().map(|t| t.to_protobuf()).collect(), + language: c.language.clone(), + link: c.link.clone(), + identifier: c.identifier.clone(), + body: c.body.clone(), + compressed_binary: c.compressed_binary.clone(), + function_type: c.function_type.clone(), + runtime: c.runtime.clone(), + } + } +} + impl OwnedByUserCatalog for FunctionCatalog { fn owner(&self) -> u32 { self.owner diff --git a/src/frontend/src/catalog/index_catalog.rs b/src/frontend/src/catalog/index_catalog.rs index 7e13c365407e1..098eca22af9ee 100644 --- a/src/frontend/src/catalog/index_catalog.rs +++ b/src/frontend/src/catalog/index_catalog.rs @@ -21,7 +21,7 @@ use itertools::Itertools; use risingwave_common::catalog::{Field, IndexId, Schema}; use risingwave_common::util::epoch::Epoch; use risingwave_common::util::sort_util::ColumnOrder; -use risingwave_pb::catalog::{PbIndex, PbStreamJobStatus}; +use risingwave_pb::catalog::{PbIndex, PbIndexColumnProperties, PbStreamJobStatus}; use crate::catalog::{DatabaseId, OwnedByUserCatalog, SchemaId, TableCatalog}; use crate::expr::{Expr, ExprDisplay, ExprImpl, FunctionCall}; @@ -40,6 +40,10 @@ pub struct IndexCatalog { /// The input args of `FuncCall` is also the column index of the primary table. pub index_item: Vec, + /// The properties of the index columns. + /// + pub index_column_properties: Vec, + pub index_table: Arc, pub primary_table: Arc, @@ -112,6 +116,7 @@ impl IndexCatalog { id: index_prost.id.into(), name: index_prost.name.clone(), index_item, + index_column_properties: index_prost.index_column_properties.clone(), index_table: Arc::new(index_table.clone()), primary_table: Arc::new(primary_table.clone()), primary_to_secondary_mapping, @@ -179,6 +184,7 @@ impl IndexCatalog { .iter() .map(|expr| expr.to_expr_proto()) .collect_vec(), + index_column_properties: self.index_column_properties.clone(), index_columns_len: self.index_columns_len, initialized_at_epoch: self.initialized_at_epoch.map(|e| e.0), created_at_epoch: self.created_at_epoch.map(|e| e.0), @@ -188,6 +194,11 @@ impl IndexCatalog { } } + /// Get the column properties of the index column. + pub fn get_column_properties(&self, column_idx: usize) -> Option { + self.index_column_properties.get(column_idx).cloned() + } + pub fn get_column_def(&self, column_idx: usize) -> Option { if let Some(col) = self.index_table.columns.get(column_idx) { if col.is_hidden { diff --git a/src/frontend/src/catalog/mod.rs b/src/frontend/src/catalog/mod.rs index 687fa5ac350e7..64c3f525dd8f6 100644 --- a/src/frontend/src/catalog/mod.rs +++ b/src/frontend/src/catalog/mod.rs @@ -39,6 +39,8 @@ pub(crate) mod system_catalog; pub(crate) mod table_catalog; pub(crate) mod view_catalog; +pub(crate) mod secret_catalog; + pub(crate) use catalog_service::CatalogReader; pub use index_catalog::IndexCatalog; pub use table_catalog::TableCatalog; @@ -55,6 +57,7 @@ pub(crate) type SchemaId = u32; pub(crate) type TableId = risingwave_common::catalog::TableId; pub(crate) type ColumnId = risingwave_common::catalog::ColumnId; pub(crate) type FragmentId = u32; +pub(crate) type SecretId = risingwave_common::catalog::SecretId; /// Check if the column name does not conflict with the internally reserved column name. pub fn check_valid_column_name(column_name: &str) -> Result<()> { diff --git a/src/frontend/src/catalog/root_catalog.rs b/src/frontend/src/catalog/root_catalog.rs index 88e75f4ad90d6..85c76927be773 100644 --- a/src/frontend/src/catalog/root_catalog.rs +++ b/src/frontend/src/catalog/root_catalog.rs @@ -21,8 +21,8 @@ use risingwave_common::session_config::{SearchPath, USER_NAME_WILD_CARD}; use risingwave_common::types::DataType; use risingwave_connector::sink::catalog::SinkCatalog; use risingwave_pb::catalog::{ - PbConnection, PbDatabase, PbFunction, PbIndex, PbSchema, PbSink, PbSource, PbSubscription, - PbTable, PbView, + PbConnection, PbDatabase, PbFunction, PbIndex, PbSchema, PbSecret, PbSink, PbSource, + PbSubscription, PbTable, PbView, }; use risingwave_pb::hummock::HummockVersionStats; @@ -30,10 +30,13 @@ use super::function_catalog::FunctionCatalog; use super::source_catalog::SourceCatalog; use super::subscription_catalog::SubscriptionCatalog; use super::view_catalog::ViewCatalog; -use super::{CatalogError, CatalogResult, ConnectionId, SinkId, SourceId, SubscriptionId, ViewId}; +use super::{ + CatalogError, CatalogResult, ConnectionId, SecretId, SinkId, SourceId, SubscriptionId, ViewId, +}; use crate::catalog::connection_catalog::ConnectionCatalog; use crate::catalog::database_catalog::DatabaseCatalog; use crate::catalog::schema_catalog::SchemaCatalog; +use crate::catalog::secret_catalog::SecretCatalog; use crate::catalog::system_catalog::{ get_sys_tables_in_schema, get_sys_views_in_schema, SystemTableCatalog, }; @@ -201,6 +204,14 @@ impl Catalog { .create_subscription(proto); } + pub fn create_secret(&mut self, proto: &PbSecret) { + self.get_database_mut(proto.database_id) + .unwrap() + .get_schema_mut(proto.schema_id) + .unwrap() + .create_secret(proto); + } + pub fn create_view(&mut self, proto: &PbView) { self.get_database_mut(proto.database_id) .unwrap() @@ -257,6 +268,25 @@ impl Catalog { } } + pub fn update_secret(&mut self, proto: &PbSecret) { + let database = self.get_database_mut(proto.database_id).unwrap(); + let schema = database.get_schema_mut(proto.schema_id).unwrap(); + let secret_id = SecretId::new(proto.id); + if schema.get_secret_by_id(&secret_id).is_some() { + schema.update_secret(proto); + } else { + // Enter this branch when schema is changed by `ALTER ... SET SCHEMA ...` statement. + schema.create_secret(proto); + database + .iter_schemas_mut() + .find(|schema| { + schema.id() != proto.schema_id && schema.get_secret_by_id(&secret_id).is_some() + }) + .unwrap() + .drop_secret(secret_id); + } + } + pub fn drop_database(&mut self, db_id: DatabaseId) { let name = self.db_name_by_id.remove(&db_id).unwrap(); let database = self.database_by_name.remove(&name).unwrap(); @@ -290,7 +320,7 @@ impl Catalog { .iter_schemas_mut() .find(|schema| { schema.id() != proto.schema_id - && schema.get_table_by_id(&proto.id.into()).is_some() + && schema.get_created_table_by_id(&proto.id.into()).is_some() }) .unwrap() .drop_table(proto.id.into()); @@ -377,6 +407,14 @@ impl Catalog { .drop_sink(sink_id); } + pub fn drop_secret(&mut self, db_id: DatabaseId, schema_id: SchemaId, secret_id: SecretId) { + self.get_database_mut(db_id) + .unwrap() + .get_schema_mut(schema_id) + .unwrap() + .drop_secret(secret_id); + } + pub fn update_sink(&mut self, proto: &PbSink) { let database = self.get_database_mut(proto.database_id).unwrap(); let schema = database.get_schema_mut(proto.schema_id).unwrap(); @@ -549,7 +587,7 @@ impl Catalog { } pub fn get_table_name_by_id(&self, table_id: TableId) -> CatalogResult { - self.get_table_by_id(&table_id) + self.get_any_table_by_id(&table_id) .map(|table| table.name.clone()) } @@ -597,7 +635,9 @@ impl Catalog { )) } - pub fn get_table_by_name<'a>( + /// Used to get `TableCatalog` for Materialized Views, Tables and Indexes. + /// Retrieves all tables, created or creating. + pub fn get_any_table_by_name<'a>( &self, db_name: &str, schema_path: SchemaPath<'a>, @@ -612,21 +652,38 @@ impl Catalog { .ok_or_else(|| CatalogError::NotFound("table", table_name.to_string())) } - pub fn get_table_by_id(&self, table_id: &TableId) -> CatalogResult<&Arc> { + /// Used to get `TableCatalog` for Materialized Views, Tables and Indexes. + /// Retrieves only created tables. + pub fn get_created_table_by_name<'a>( + &self, + db_name: &str, + schema_path: SchemaPath<'a>, + table_name: &str, + ) -> CatalogResult<(&Arc, &'a str)> { + schema_path + .try_find(|schema_name| { + Ok(self + .get_schema_by_name(db_name, schema_name)? + .get_created_table_by_name(table_name)) + })? + .ok_or_else(|| CatalogError::NotFound("table", table_name.to_string())) + } + + pub fn get_any_table_by_id(&self, table_id: &TableId) -> CatalogResult<&Arc> { self.table_by_id .get(table_id) .ok_or_else(|| CatalogError::NotFound("table id", table_id.to_string())) } /// This function is similar to `get_table_by_id` expect that a table must be in a given database. - pub fn get_table_by_id_with_db( + pub fn get_created_table_by_id_with_db( &self, db_name: &str, table_id: u32, ) -> CatalogResult<&Arc> { let table_id = TableId::from(table_id); for schema in self.get_database_by_name(db_name)?.iter_schemas() { - if let Some(table) = schema.get_table_by_id(&table_id) { + if let Some(table) = schema.get_created_table_by_id(&table_id) { return Ok(table); } } @@ -663,7 +720,7 @@ impl Catalog { if found { let mut table = self - .get_table_by_id(table_id) + .get_any_table_by_id(table_id) .unwrap() .to_prost(schema_id, database_id); table.name = table_name.to_string(); @@ -792,6 +849,21 @@ impl Catalog { Err(CatalogError::NotFound("view", view_id.to_string())) } + pub fn get_secret_by_name<'a>( + &self, + db_name: &str, + schema_path: SchemaPath<'a>, + secret_name: &str, + ) -> CatalogResult<(&Arc, &'a str)> { + schema_path + .try_find(|schema_name| { + Ok(self + .get_schema_by_name(db_name, schema_name)? + .get_secret_by_name(secret_name)) + })? + .ok_or_else(|| CatalogError::NotFound("secret", secret_name.to_string())) + } + pub fn get_connection_by_name<'a>( &self, db_name: &str, @@ -885,7 +957,7 @@ impl Catalog { ) -> CatalogResult<()> { let schema = self.get_schema_by_name(db_name, schema_name)?; - if let Some(table) = schema.get_table_by_name(relation_name) { + if let Some(table) = schema.get_created_table_by_name(relation_name) { if table.is_index() { Err(CatalogError::Duplicated("index", relation_name.to_string())) } else if table.is_mview() { @@ -957,6 +1029,21 @@ impl Catalog { } } + pub fn check_secret_name_duplicated( + &self, + db_name: &str, + schema_name: &str, + secret_name: &str, + ) -> CatalogResult<()> { + let schema = self.get_schema_by_name(db_name, schema_name)?; + + if schema.get_secret_by_name(secret_name).is_some() { + Err(CatalogError::Duplicated("secret", secret_name.to_string())) + } else { + Ok(()) + } + } + /// Get the catalog cache's catalog version. pub fn version(&self) -> u64 { self.version @@ -1000,7 +1087,7 @@ impl Catalog { #[allow(clippy::manual_map)] if let Some(item) = schema.get_system_table_by_name(class_name) { Ok(Some(item.id().into())) - } else if let Some(item) = schema.get_table_by_name(class_name) { + } else if let Some(item) = schema.get_created_table_by_name(class_name) { Ok(Some(item.id().into())) } else if let Some(item) = schema.get_index_by_name(class_name) { Ok(Some(item.id.into())) diff --git a/src/frontend/src/catalog/schema_catalog.rs b/src/frontend/src/catalog/schema_catalog.rs index 56de1d743da41..61ec11e144dc4 100644 --- a/src/frontend/src/catalog/schema_catalog.rs +++ b/src/frontend/src/catalog/schema_catalog.rs @@ -17,24 +17,27 @@ use std::collections::HashMap; use std::sync::Arc; use itertools::Itertools; -use risingwave_common::catalog::{valid_table_name, FunctionId, IndexId, TableId}; +use risingwave_common::catalog::{valid_table_name, FunctionId, IndexId, StreamJobStatus, TableId}; use risingwave_common::types::DataType; use risingwave_connector::sink::catalog::SinkCatalog; pub use risingwave_expr::sig::*; use risingwave_pb::catalog::{ - PbConnection, PbFunction, PbIndex, PbSchema, PbSink, PbSource, PbSubscription, PbTable, PbView, + PbConnection, PbFunction, PbIndex, PbSchema, PbSecret, PbSink, PbSource, PbSubscription, + PbTable, PbView, }; +use risingwave_pb::user::grant_privilege::Object; use super::subscription_catalog::SubscriptionCatalog; use super::{OwnedByUserCatalog, SubscriptionId}; use crate::catalog::connection_catalog::ConnectionCatalog; use crate::catalog::function_catalog::FunctionCatalog; use crate::catalog::index_catalog::IndexCatalog; +use crate::catalog::secret_catalog::SecretCatalog; use crate::catalog::source_catalog::SourceCatalog; use crate::catalog::system_catalog::SystemTableCatalog; use crate::catalog::table_catalog::TableCatalog; use crate::catalog::view_catalog::ViewCatalog; -use crate::catalog::{ConnectionId, DatabaseId, SchemaId, SinkId, SourceId, ViewId}; +use crate::catalog::{ConnectionId, DatabaseId, SchemaId, SecretId, SinkId, SourceId, ViewId}; use crate::expr::{infer_type_name, infer_type_with_sigmap, Expr, ExprImpl}; use crate::user::UserId; @@ -61,6 +64,11 @@ pub struct SchemaCatalog { function_by_id: HashMap>, connection_by_name: HashMap>, connection_by_id: HashMap>, + secret_by_name: HashMap>, + secret_by_id: HashMap>, + + _secret_source_ref: HashMap>, + _secret_sink_ref: HashMap>, // This field is currently used only for `show connections` connection_source_ref: HashMap>, @@ -120,9 +128,11 @@ impl SchemaCatalog { let name = prost.name.clone(); let id = prost.id.into(); let old_index = self.index_by_id.get(&id).unwrap(); - let index_table = self.get_table_by_id(&prost.index_table_id.into()).unwrap(); + let index_table = self + .get_created_table_by_id(&prost.index_table_id.into()) + .unwrap(); let primary_table = self - .get_table_by_id(&prost.primary_table_id.into()) + .get_created_table_by_id(&prost.primary_table_id.into()) .unwrap(); let index: IndexCatalog = IndexCatalog::build_from(prost, index_table, primary_table); let index_ref = Arc::new(index); @@ -159,9 +169,11 @@ impl SchemaCatalog { let name = prost.name.clone(); let id = prost.id.into(); - let index_table = self.get_table_by_id(&prost.index_table_id.into()).unwrap(); + let index_table = self + .get_created_table_by_id(&prost.index_table_id.into()) + .unwrap(); let primary_table = self - .get_table_by_id(&prost.primary_table_id.into()) + .get_created_table_by_id(&prost.primary_table_id.into()) .unwrap(); let index: IndexCatalog = IndexCatalog::build_from(prost, index_table, primary_table); let index_ref = Arc::new(index); @@ -483,6 +495,46 @@ impl SchemaCatalog { .expect("connection not found by name"); } + pub fn create_secret(&mut self, prost: &PbSecret) { + let name = prost.name.clone(); + let id = SecretId::new(prost.id); + let secret = SecretCatalog::from(prost); + let secret_ref = Arc::new(secret); + + self.secret_by_id + .try_insert(id, secret_ref.clone()) + .unwrap(); + self.secret_by_name + .try_insert(name, secret_ref.clone()) + .unwrap(); + } + + pub fn update_secret(&mut self, prost: &PbSecret) { + let name = prost.name.clone(); + let id = SecretId::new(prost.id); + let secret = SecretCatalog::from(prost); + let secret_ref = Arc::new(secret); + + let old_secret = self.secret_by_id.get(&id).unwrap(); + // check if secret name get updated. + if old_secret.name != name { + self.secret_by_name.remove(&old_secret.name); + } + + self.secret_by_name.insert(name, secret_ref.clone()); + self.secret_by_id.insert(id, secret_ref); + } + + pub fn drop_secret(&mut self, secret_id: SecretId) { + let secret_ref = self + .secret_by_id + .remove(&secret_id) + .expect("secret not found by id"); + self.secret_by_name + .remove(&secret_ref.name) + .expect("secret not found by name"); + } + pub fn iter_all(&self) -> impl Iterator> { self.table_by_name.values() } @@ -508,10 +560,10 @@ impl SchemaCatalog { } /// Iterate all materialized views, excluding the indices. - pub fn iter_mv(&self) -> impl Iterator> { + pub fn iter_created_mvs(&self) -> impl Iterator> { self.table_by_name .iter() - .filter(|(_, v)| v.is_mview() && valid_table_name(&v.name)) + .filter(|(_, v)| v.is_mview() && valid_table_name(&v.name) && v.is_created()) .map(|(_, v)| v) } @@ -545,6 +597,10 @@ impl SchemaCatalog { self.connection_by_name.values() } + pub fn iter_secret(&self) -> impl Iterator> { + self.secret_by_name.values() + } + pub fn iter_system_tables(&self) -> impl Iterator> { self.system_table_by_name.values() } @@ -553,10 +609,22 @@ impl SchemaCatalog { self.table_by_name.get(table_name) } + pub fn get_created_table_by_name(&self, table_name: &str) -> Option<&Arc> { + self.table_by_name + .get(table_name) + .filter(|&table| table.stream_job_status == StreamJobStatus::Created) + } + pub fn get_table_by_id(&self, table_id: &TableId) -> Option<&Arc> { self.table_by_id.get(table_id) } + pub fn get_created_table_by_id(&self, table_id: &TableId) -> Option<&Arc> { + self.table_by_id + .get(table_id) + .filter(|&table| table.stream_job_status == StreamJobStatus::Created) + } + pub fn get_view_by_name(&self, view_name: &str) -> Option<&Arc> { self.view_by_name.get(view_name) } @@ -686,6 +754,14 @@ impl SchemaCatalog { self.connection_by_name.get(connection_name) } + pub fn get_secret_by_name(&self, secret_name: &str) -> Option<&Arc> { + self.secret_by_name.get(secret_name) + } + + pub fn get_secret_by_id(&self, secret_id: &SecretId) -> Option<&Arc> { + self.secret_by_id.get(secret_id) + } + /// get all sources referencing the connection pub fn get_source_ids_by_connection( &self, @@ -703,6 +779,23 @@ impl SchemaCatalog { .map(|s| s.to_owned()) } + pub fn get_grant_object_by_oid(&self, oid: u32) -> Option { + #[allow(clippy::manual_map)] + if self.get_created_table_by_id(&TableId::new(oid)).is_some() + || self.get_index_by_id(&IndexId::new(oid)).is_some() + { + Some(Object::TableId(oid)) + } else if self.get_source_by_id(&oid).is_some() { + Some(Object::SourceId(oid)) + } else if self.get_sink_by_id(&oid).is_some() { + Some(Object::SinkId(oid)) + } else if self.get_view_by_id(&oid).is_some() { + Some(Object::ViewId(oid)) + } else { + None + } + } + pub fn id(&self) -> SchemaId { self.id } @@ -746,6 +839,10 @@ impl From<&PbSchema> for SchemaCatalog { function_by_id: HashMap::new(), connection_by_name: HashMap::new(), connection_by_id: HashMap::new(), + secret_by_name: HashMap::new(), + secret_by_id: HashMap::new(), + _secret_source_ref: HashMap::new(), + _secret_sink_ref: HashMap::new(), connection_source_ref: HashMap::new(), connection_sink_ref: HashMap::new(), subscription_by_name: HashMap::new(), diff --git a/src/frontend/src/catalog/secret_catalog.rs b/src/frontend/src/catalog/secret_catalog.rs new file mode 100644 index 0000000000000..5e9aaae7dec99 --- /dev/null +++ b/src/frontend/src/catalog/secret_catalog.rs @@ -0,0 +1,45 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_pb::catalog::PbSecret; + +use crate::catalog::{DatabaseId, OwnedByUserCatalog, SecretId}; +use crate::user::UserId; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct SecretCatalog { + pub secret_id: SecretId, + pub name: String, + pub database_id: DatabaseId, + pub value: Vec, + pub owner: UserId, +} + +impl From<&PbSecret> for SecretCatalog { + fn from(value: &PbSecret) -> Self { + Self { + secret_id: SecretId::new(value.id), + database_id: value.database_id, + owner: value.owner, + name: value.name.clone(), + value: value.value.clone(), + } + } +} + +impl OwnedByUserCatalog for SecretCatalog { + fn owner(&self) -> UserId { + self.owner + } +} diff --git a/src/frontend/src/catalog/source_catalog.rs b/src/frontend/src/catalog/source_catalog.rs index 17292b1324ed2..1ee095a918e5d 100644 --- a/src/frontend/src/catalog/source_catalog.rs +++ b/src/frontend/src/catalog/source_catalog.rs @@ -77,6 +77,7 @@ impl SourceCatalog { version: self.version, created_at_cluster_version: self.created_at_cluster_version.clone(), initialized_at_cluster_version: self.initialized_at_cluster_version.clone(), + secret_refs: Default::default(), } } diff --git a/src/frontend/src/catalog/subscription_catalog.rs b/src/frontend/src/catalog/subscription_catalog.rs index 1409948e07bd9..36a5a71a0e9be 100644 --- a/src/frontend/src/catalog/subscription_catalog.rs +++ b/src/frontend/src/catalog/subscription_catalog.rs @@ -13,18 +13,17 @@ // limitations under the License. use core::str::FromStr; -use std::collections::{BTreeMap, HashSet}; -use itertools::Itertools; -use risingwave_common::catalog::{ColumnCatalog, TableId, UserId, OBJECT_ID_PLACEHOLDER}; +use risingwave_common::catalog::{TableId, UserId, OBJECT_ID_PLACEHOLDER}; use risingwave_common::types::Interval; use risingwave_common::util::epoch::Epoch; -use risingwave_common::util::sort_util::ColumnOrder; -use risingwave_pb::catalog::{PbStreamJobStatus, PbSubscription}; +use risingwave_pb::catalog::subscription::PbSubscriptionState; +use risingwave_pb::catalog::PbSubscription; use thiserror_ext::AsReport; use super::OwnedByUserCatalog; use crate::error::{ErrorCode, Result}; +use crate::WithOptions; #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(test, derive(Default))] @@ -38,21 +37,8 @@ pub struct SubscriptionCatalog { /// Full SQL definition of the subscription. For debug now. pub definition: String, - /// All columns of the subscription. Note that this is NOT sorted by columnId in the vector. - pub columns: Vec, - - /// Primiary keys of the subscription. Derived by the frontend. - pub plan_pk: Vec, - - /// Distribution key indices of the subscription. For example, if `distribution_key = [1, 2]`, then the - /// distribution keys will be `columns[1]` and `columns[2]`. - pub distribution_key: Vec, - - /// The properties of the subscription, only `retention`. - pub properties: BTreeMap, - - /// The upstream table name on which the subscription depends - pub subscription_from_name: String, + /// The retention seconds of the subscription. + pub retention_seconds: u64, /// The database id pub database_id: u32, @@ -61,7 +47,7 @@ pub struct SubscriptionCatalog { pub schema_id: u32, /// The subscription depends on the upstream list - pub dependent_relations: Vec, + pub dependent_table_id: TableId, /// The user id pub owner: UserId, @@ -71,7 +57,6 @@ pub struct SubscriptionCatalog { pub created_at_cluster_version: Option, pub initialized_at_cluster_version: Option, - pub subscription_internal_table_name: Option, } #[derive(Clone, Copy, Debug, Default, Hash, PartialOrd, PartialEq, Eq, Ord)] @@ -97,14 +82,8 @@ impl SubscriptionId { } impl SubscriptionCatalog { - pub fn add_dependent_relations(mut self, mut dependent_relations: HashSet) -> Self { - dependent_relations.extend(self.dependent_relations); - self.dependent_relations = dependent_relations.into_iter().collect(); - self - } - - pub fn get_retention_seconds(&self) -> Result { - let retention_seconds_str = self.properties.get("retention").ok_or_else(|| { + pub fn set_retention_seconds(&mut self, properties: &WithOptions) -> Result<()> { + let retention_seconds_str = properties.get("retention").ok_or_else(|| { ErrorCode::InternalError("Subscription retention time not set.".to_string()) })?; let retention_seconds = (Interval::from_str(retention_seconds_str) @@ -116,47 +95,29 @@ impl SubscriptionCatalog { })? .epoch_in_micros() / 1000000) as u64; - - Ok(retention_seconds) + self.retention_seconds = retention_seconds; + Ok(()) } pub fn create_sql(&self) -> String { self.definition.clone() } - pub fn get_log_store_name(&self) -> String { - self.subscription_internal_table_name.clone().unwrap() - } - pub fn to_proto(&self) -> PbSubscription { - assert!(!self.dependent_relations.is_empty()); PbSubscription { id: self.id.subscription_id, name: self.name.clone(), definition: self.definition.clone(), - column_catalogs: self - .columns - .iter() - .map(|column| column.to_protobuf()) - .collect_vec(), - plan_pk: self.plan_pk.iter().map(|k| k.to_protobuf()).collect_vec(), - distribution_key: self.distribution_key.iter().map(|k| *k as _).collect_vec(), - subscription_from_name: self.subscription_from_name.clone(), - properties: self.properties.clone().into_iter().collect(), + retention_seconds: self.retention_seconds, database_id: self.database_id, schema_id: self.schema_id, - dependent_relations: self - .dependent_relations - .iter() - .map(|k| k.table_id) - .collect_vec(), initialized_at_epoch: self.initialized_at_epoch.map(|e| e.0), created_at_epoch: self.created_at_epoch.map(|e| e.0), owner: self.owner.into(), - stream_job_status: PbStreamJobStatus::Creating.into(), initialized_at_cluster_version: self.initialized_at_cluster_version.clone(), created_at_cluster_version: self.created_at_cluster_version.clone(), - subscription_internal_table_name: self.subscription_internal_table_name.clone(), + dependent_table_id: self.dependent_table_id.table_id, + subscription_state: PbSubscriptionState::Init.into(), } } } @@ -167,32 +128,15 @@ impl From<&PbSubscription> for SubscriptionCatalog { id: SubscriptionId::new(prost.id), name: prost.name.clone(), definition: prost.definition.clone(), - columns: prost - .column_catalogs - .iter() - .map(|c| ColumnCatalog::from(c.clone())) - .collect_vec(), - plan_pk: prost - .plan_pk - .iter() - .map(ColumnOrder::from_protobuf) - .collect_vec(), - distribution_key: prost.distribution_key.iter().map(|k| *k as _).collect_vec(), - subscription_from_name: prost.subscription_from_name.clone(), - properties: prost.properties.clone().into_iter().collect(), + retention_seconds: prost.retention_seconds, database_id: prost.database_id, schema_id: prost.schema_id, - dependent_relations: prost - .dependent_relations - .iter() - .map(|k| TableId::new(*k)) - .collect_vec(), + dependent_table_id: TableId::new(prost.dependent_table_id), owner: prost.owner.into(), created_at_epoch: prost.created_at_epoch.map(Epoch::from), initialized_at_epoch: prost.initialized_at_epoch.map(Epoch::from), created_at_cluster_version: prost.created_at_cluster_version.clone(), initialized_at_cluster_version: prost.initialized_at_cluster_version.clone(), - subscription_internal_table_name: prost.subscription_internal_table_name.clone(), } } } diff --git a/src/frontend/src/catalog/system_catalog/information_schema/columns.rs b/src/frontend/src/catalog/system_catalog/information_schema/columns.rs index 531b8b3012c8f..b7e5889d16c27 100644 --- a/src/frontend/src/catalog/system_catalog/information_schema/columns.rs +++ b/src/frontend/src/catalog/system_catalog/information_schema/columns.rs @@ -31,6 +31,7 @@ use risingwave_frontend_macro::system_catalog; NULL AS column_default, NULL::integer AS character_maximum_length, NULL::integer AS numeric_precision, + NULL::integer AS numeric_precision_radix, NULL::integer AS numeric_scale, NULL::integer AS datetime_precision, c.position AS ordinal_position, @@ -81,6 +82,7 @@ struct Column { column_default: String, character_maximum_length: i32, numeric_precision: i32, + numeric_precision_radix: i32, numeric_scale: i32, datetime_precision: i32, ordinal_position: i32, diff --git a/src/frontend/src/catalog/system_catalog/pg_catalog/mod.rs b/src/frontend/src/catalog/system_catalog/pg_catalog/mod.rs index 202646b55a603..3d1c8f2a56f74 100644 --- a/src/frontend/src/catalog/system_catalog/pg_catalog/mod.rs +++ b/src/frontend/src/catalog/system_catalog/pg_catalog/mod.rs @@ -29,7 +29,6 @@ mod pg_extension; mod pg_index; mod pg_indexes; mod pg_inherits; -mod pg_keywords; mod pg_language; mod pg_locks; mod pg_matviews; @@ -52,5 +51,3 @@ mod pg_trigger; mod pg_type; mod pg_user; mod pg_views; - -pub use pg_keywords::*; diff --git a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_attribute.rs b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_attribute.rs index a0bf9190df2cb..89197e8f95dab 100644 --- a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_attribute.rs +++ b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_attribute.rs @@ -39,6 +39,7 @@ use risingwave_frontend_macro::system_catalog; ELSE ''::varchar END AS attgenerated, -1 AS atttypmod, + NULL::text[] AS attoptions, 0 AS attcollation FROM rw_catalog.rw_columns c WHERE c.is_hidden = false" @@ -56,5 +57,6 @@ struct PgAttribute { attidentity: String, attgenerated: String, atttypmod: i32, + attoptions: Vec, attcollation: i32, } diff --git a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_index.rs b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_index.rs index b91bd9b698cbe..f370b8615ad48 100644 --- a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_index.rs +++ b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_index.rs @@ -39,7 +39,34 @@ use risingwave_frontend_macro::system_catalog; true AS indisready, true AS indislive, false AS indisreplident - FROM rw_catalog.rw_indexes" + FROM rw_catalog.rw_indexes + UNION ALL + SELECT c.relation_id AS indexrelid, + c.relation_id AS indrelid, + COUNT(*)::smallint AS indnatts, + COUNT(*)::smallint AS indnkeyatts, + true AS indisunique, + ARRAY_AGG(c.position)::smallint[] AS indkey, + ARRAY[]::smallint[] as indoption, + NULL AS indexprs, + NULL AS indpred, + TRUE AS indisprimary, + ARRAY[]::int[] AS indclass, + false AS indisexclusion, + true AS indimmediate, + false AS indisclustered, + true AS indisvalid, + false AS indcheckxmin, + true AS indisready, + true AS indislive, + false AS indisreplident + FROM rw_catalog.rw_columns c + WHERE c.is_primary_key = true AND c.is_hidden = false + AND c.relation_id IN ( + SELECT id + FROM rw_catalog.rw_tables + ) + GROUP BY c.relation_id" )] #[derive(Fields)] struct PgIndex { diff --git a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_indexes.rs b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_indexes.rs index e35aba9567ae2..a602e71804777 100644 --- a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_indexes.rs +++ b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_indexes.rs @@ -28,6 +28,19 @@ use risingwave_frontend_macro::system_catalog; FROM rw_catalog.rw_indexes i JOIN rw_catalog.rw_tables t ON i.primary_table_id = t.id JOIN rw_catalog.rw_schemas s ON i.schema_id = s.id + UNION ALL + SELECT s.name AS schemaname, + t.name AS tablename, + concat(t.name, '_pkey') AS indexname, + NULL AS tablespace, + '' AS indexdef + FROM rw_catalog.rw_tables t + JOIN rw_catalog.rw_schemas s ON t.schema_id = s.id + WHERE t.id IN ( + SELECT DISTINCT relation_id + FROM rw_catalog.rw_columns + WHERE is_primary_key = true AND is_hidden = false + ) " )] #[derive(Fields)] diff --git a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_keywords.rs b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_keywords.rs deleted file mode 100644 index c3bb43ef0a842..0000000000000 --- a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_keywords.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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 risingwave_common::types::Fields; -use risingwave_frontend_macro::system_catalog; - -pub const PG_KEYWORDS_TABLE_NAME: &str = "pg_keywords"; -pub const PG_GET_KEYWORDS_FUNC_NAME: &str = "pg_get_keywords"; - -/// The catalog `pg_keywords` stores keywords. `pg_get_keywords` returns the content of this table. -/// Ref: [`https://www.postgresql.org/docs/15/functions-info.html`] -// TODO: change to read reserved keywords here -#[system_catalog(view, "pg_catalog.pg_keywords")] -#[derive(Fields)] -struct PgKeywords { - word: String, - catcode: String, - catdesc: String, -} diff --git a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_tables.rs b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_tables.rs index 07b3a15e09b1b..27b4c214f0265 100644 --- a/src/frontend/src/catalog/system_catalog/pg_catalog/pg_tables.rs +++ b/src/frontend/src/catalog/system_catalog/pg_catalog/pg_tables.rs @@ -34,7 +34,8 @@ use risingwave_frontend_macro::system_catalog; schema_id, owner FROM rw_catalog.rw_system_tables) AS t - JOIN rw_catalog.rw_schemas s ON t.schema_id = s.id" + JOIN rw_catalog.rw_schemas s ON t.schema_id = s.id + AND s.name <> 'rw_catalog'" )] #[derive(Fields)] struct PgTable { diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/mod.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/mod.rs index 502c41561b65f..4e0e165a6d524 100644 --- a/src/frontend/src/catalog/system_catalog/rw_catalog/mod.rs +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/mod.rs @@ -56,3 +56,6 @@ mod rw_user_secrets; mod rw_users; mod rw_views; mod rw_worker_nodes; + +mod rw_actor_id_to_ddl; +mod rw_fragment_id_to_ddl; diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_actor_id_to_ddl.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_actor_id_to_ddl.rs new file mode 100644 index 0000000000000..95e269f0d5d5c --- /dev/null +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_actor_id_to_ddl.rs @@ -0,0 +1,42 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_common::types::Fields; +use risingwave_frontend_macro::system_catalog; + +/// Provides a mapping from `actor_id` to its ddl info. +#[system_catalog( +view, +"rw_catalog.rw_actor_id_to_ddl", +"with + actor_to_job_id as (select actor_id, a.fragment_id, table_id from rw_fragments f join rw_actors a on f.fragment_id = a.fragment_id), + job_id_to_mv as (select actor_id, fragment_id, d.id as job_id, schema_id, 'mv' as ddl_type, name from rw_materialized_views d join actor_to_job_id a on d.id = a.table_id), + job_id_to_sink as (select actor_id, fragment_id, d.id as job_id, schema_id, 'sink' as ddl_type, name from rw_sinks d join actor_to_job_id a on d.id = a.table_id), + job_id_to_source as (select actor_id, fragment_id, d.id as job_id, schema_id, 'source' as ddl_type, name from rw_sources d join actor_to_job_id a on d.id = a.table_id), + job_id_to_table as (select actor_id, fragment_id, d.id as job_id, schema_id, 'table' as ddl_type, name from rw_tables d join actor_to_job_id a on d.id = a.table_id) + select * from job_id_to_mv + union all select * from job_id_to_sink + union all select * from job_id_to_source + union all select * from job_id_to_table" +)] +#[derive(Fields)] +struct RwActorIdToDdl { + #[primary_key] + actor_id: i32, + fragment_id: i32, + job_id: i32, + schema_id: i32, + ddl_type: String, + name: String, +} diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_fragment_id_to_ddl.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_fragment_id_to_ddl.rs new file mode 100644 index 0000000000000..094e85903a31a --- /dev/null +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_fragment_id_to_ddl.rs @@ -0,0 +1,40 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_common::types::Fields; +use risingwave_frontend_macro::system_catalog; + +/// Provides a mapping from `actor_id` to its ddl info. +#[system_catalog( +view, +"rw_catalog.rw_fragment_id_to_ddl", +"with + job_id_to_mv as (select fragment_id, d.id as job_id, schema_id, 'mv' as ddl_type, name from rw_materialized_views d join rw_fragments f on d.id = f.table_id), + job_id_to_sink as (select fragment_id, d.id as job_id, schema_id, 'sink' as ddl_type, name from rw_sinks d join rw_fragments f on d.id = f.table_id), + job_id_to_source as (select fragment_id, d.id as job_id, schema_id, 'source' as ddl_type, name from rw_sources d join rw_fragments f on d.id = f.table_id), + job_id_to_table as (select fragment_id, d.id as job_id, schema_id, 'table' as ddl_type, name from rw_tables d join rw_fragments f on d.id = f.table_id) + select * from job_id_to_mv + union all select * from job_id_to_sink + union all select * from job_id_to_source + union all select * from job_id_to_table" +)] +#[derive(Fields)] +struct RwFragmentIdToDdl { + #[primary_key] + fragment_id: i32, + job_id: i32, + schema_id: i32, + ddl_type: String, + name: String, +} diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs index 443fa255f4398..43f62aaee2105 100644 --- a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs @@ -19,11 +19,11 @@ use crate::catalog::system_catalog::SysCatalogReaderImpl; use crate::error::Result; #[derive(Fields)] -#[primary_key(object_id, sst_id)] // TODO: is this correct? +#[primary_key(object_id, compaction_group_id)] struct RwHummockBranchedObject { object_id: i64, - sst_id: i64, compaction_group_id: i64, + sst_id: Vec, } #[system_catalog(table, "rw_catalog.rw_hummock_branched_objects")] @@ -33,7 +33,7 @@ async fn read(reader: &SysCatalogReaderImpl) -> Result Vec Result> { + let version = reader.meta_client.get_hummock_current_version().await?; + Ok(version + .state_table_info + .info() + .iter() + .map(|(table_id, info)| RwHummockSnapshot { + table_id: table_id.table_id as _, + committed_epoch: info.committed_epoch as _, + safe_epoch: info.safe_epoch as _, + }) + .collect()) +} diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_files.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_files.rs index 7afe2561e1775..aaeb8aaa064c9 100644 --- a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_files.rs +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_files.rs @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; use std::ops::Deref; use anyhow::anyhow; -use icelake::Table; +use futures::StreamExt; +use iceberg::spec::ManifestList; +use iceberg::table::Table; use risingwave_common::types::Fields; use risingwave_connector::sink::iceberg::IcebergConfig; use risingwave_connector::source::ConnectorProperties; @@ -44,10 +45,10 @@ struct RwIcebergFiles { /// Total file size in bytes pub file_size_in_bytes: i64, /// Field ids used to determine row equality in equality delete files. - /// Required when content is EqualityDeletes and should be null + /// Required when content is `EqualityDeletes` and should be null /// otherwise. Fields with ids listed in this column must be present /// in the delete file - pub equality_ids: Option>, + pub equality_ids: Vec, /// ID representing sort order for this file. /// /// If sort order ID is missing or unknown, then the order is assumed to @@ -78,31 +79,41 @@ async fn read(reader: &SysCatalogReaderImpl) -> Result> { let mut result = vec![]; for (schema_name, source) in iceberg_sources { - let source_props: HashMap = - HashMap::from_iter(source.with_properties.clone()); + let source_props = source.with_properties.clone(); let config = ConnectorProperties::extract(source_props, false)?; if let ConnectorProperties::Iceberg(iceberg_properties) = config { let iceberg_config: IcebergConfig = iceberg_properties.to_iceberg_config(); - let table: Table = iceberg_config.load_table().await?; - result.extend( - table - .current_data_files() + let table: Table = iceberg_config.load_table_v2().await?; + if let Some(snapshot) = table.metadata().current_snapshot() { + let manifest_list: ManifestList = snapshot + .load_manifest_list(table.file_io(), table.metadata()) .await - .map_err(|e| anyhow!(e))? - .iter() - .map(|file| RwIcebergFiles { - source_id: source.id as i32, - schema_name: schema_name.clone(), - source_name: source.name.clone(), - content: file.content as i32, - file_path: file.file_path.clone(), - file_format: file.file_format.to_string(), - record_count: file.record_count, - file_size_in_bytes: file.file_size_in_bytes, - equality_ids: file.equality_ids.clone(), - sort_order_id: file.sort_order_id, - }), - ); + .map_err(|e| anyhow!(e))?; + for entry in manifest_list.entries() { + let manifest = entry + .load_manifest(table.file_io()) + .await + .map_err(|e| anyhow!(e))?; + let mut manifest_entries_stream = + futures::stream::iter(manifest.entries().iter().filter(|e| e.is_alive())); + + while let Some(manifest_entry) = manifest_entries_stream.next().await { + let file = manifest_entry.data_file(); + result.push(RwIcebergFiles { + source_id: source.id as i32, + schema_name: schema_name.clone(), + source_name: source.name.clone(), + content: file.content_type() as i32, + file_path: file.file_path().to_string(), + file_format: file.file_format().to_string(), + record_count: file.record_count() as i64, + file_size_in_bytes: file.file_size_in_bytes() as i64, + equality_ids: file.equality_ids().to_vec(), + sort_order_id: file.sort_order_id(), + }); + } + } + } } else { unreachable!() } diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_snapshots.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_snapshots.rs index 671cbcee89529..d7491cfeb0caf 100644 --- a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_snapshots.rs +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_iceberg_snapshots.rs @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; use std::ops::Deref; -use icelake::Table; +use iceberg::table::Table; use jsonbb::{Value, ValueRef}; use risingwave_common::types::{Fields, JsonbVal, Timestamptz}; use risingwave_connector::sink::iceberg::IcebergConfig; @@ -58,32 +57,31 @@ async fn read(reader: &SysCatalogReaderImpl) -> Result> let mut result = vec![]; for (schema_name, source) in iceberg_sources { - let source_props: HashMap = - HashMap::from_iter(source.with_properties.clone()); + let source_props = source.with_properties.clone(); let config = ConnectorProperties::extract(source_props, false)?; if let ConnectorProperties::Iceberg(iceberg_properties) = config { let iceberg_config: IcebergConfig = iceberg_properties.to_iceberg_config(); - let table: Table = iceberg_config.load_table().await?; - if let Some(snapshots) = &table.current_table_metadata().snapshots { - result.extend(snapshots.iter().map(|snapshot| { - RwIcebergSnapshots { - source_id: source.id as i32, - schema_name: schema_name.clone(), - source_name: source.name.clone(), - sequence_number: snapshot.sequence_number, - snapshot_id: snapshot.snapshot_id, - timestamp_ms: Timestamptz::from_millis(snapshot.timestamp_ms), - manifest_list: snapshot.manifest_list.clone(), - summary: Value::object( - snapshot - .summary - .iter() - .map(|(k, v)| (k.as_str(), ValueRef::String(v))), - ) - .into(), - } - })); - } + let table: Table = iceberg_config.load_table_v2().await?; + + result.extend(table.metadata().snapshots().map(|snapshot| { + RwIcebergSnapshots { + source_id: source.id as i32, + schema_name: schema_name.clone(), + source_name: source.name.clone(), + sequence_number: snapshot.sequence_number(), + snapshot_id: snapshot.snapshot_id(), + timestamp_ms: Timestamptz::from_millis(snapshot.timestamp().timestamp_millis()), + manifest_list: snapshot.manifest_list().to_string(), + summary: Value::object( + snapshot + .summary() + .other + .iter() + .map(|(k, v)| (k.as_str(), ValueRef::String(v))), + ) + .into(), + } + })); } } Ok(result) diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_materialized_views.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_materialized_views.rs index 16a1d7cf19d21..95b469debc2a0 100644 --- a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_materialized_views.rs +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_materialized_views.rs @@ -44,7 +44,7 @@ fn read_rw_materialized_views(reader: &SysCatalogReaderImpl) -> Result, - // the largest epoch this meta snapshot includes - max_committed_epoch: i64, - // human-readable timestamp of max_committed_epoch - max_committed_epoch_ts: Option, remarks: Option, + state_table_info: Option, + rw_version: Option, } #[system_catalog(table, "rw_catalog.rw_meta_snapshot")] async fn read_meta_snapshot(reader: &SysCatalogReaderImpl) -> Result> { - let try_get_date_time = |epoch: u64| { - if epoch == 0 { - return None; - } - let time_millis = Epoch::from(epoch).as_unix_millis(); - Timestamp::with_secs_nsecs( - (time_millis / 1000) as i64, - (time_millis % 1000 * 1_000_000) as u32, - ) - .ok() - }; let meta_snapshots = reader .meta_client .list_meta_snapshots() @@ -56,11 +39,9 @@ async fn read_meta_snapshot(reader: &SysCatalogReaderImpl) -> Result Result Result, row_encode: Option, append_only: bool, + associated_table_id: Option, connection_id: Option, definition: String, acl: String, @@ -51,40 +52,38 @@ fn read_rw_sources_info(reader: &SysCatalogReaderImpl) -> Result> Ok(schemas .flat_map(|schema| { - schema - .iter_source() - .filter(|s| s.associated_table_id.is_none()) - .map(|source| RwSource { - id: source.id as i32, - name: source.name.clone(), - schema_id: schema.id() as i32, - owner: source.owner as i32, - connector: source - .with_properties - .get(UPSTREAM_SOURCE_KEY) - .cloned() - .unwrap_or("".to_string()) - .to_uppercase(), - columns: source.columns.iter().map(|c| c.name().into()).collect(), - format: source - .info - .get_format() - .ok() - .map(|format| format.as_str_name().into()), - row_encode: source - .info - .get_row_encode() - .ok() - .map(|row_encode| row_encode.as_str_name().into()), - append_only: source.append_only, - connection_id: source.connection_id.map(|id| id as i32), - definition: source.create_sql(), - acl: get_acl_items(&Object::SourceId(source.id), false, &users, username_map), - initialized_at: source.initialized_at_epoch.map(|e| e.as_timestamptz()), - created_at: source.created_at_epoch.map(|e| e.as_timestamptz()), - initialized_at_cluster_version: source.initialized_at_cluster_version.clone(), - created_at_cluster_version: source.created_at_cluster_version.clone(), - }) + schema.iter_source().map(|source| RwSource { + id: source.id as i32, + name: source.name.clone(), + schema_id: schema.id() as i32, + owner: source.owner as i32, + connector: source + .with_properties + .get(UPSTREAM_SOURCE_KEY) + .cloned() + .unwrap_or("".to_string()) + .to_uppercase(), + columns: source.columns.iter().map(|c| c.name().into()).collect(), + format: source + .info + .get_format() + .ok() + .map(|format| format.as_str_name().into()), + row_encode: source + .info + .get_row_encode() + .ok() + .map(|row_encode| row_encode.as_str_name().into()), + append_only: source.append_only, + associated_table_id: source.associated_table_id.map(|id| id.table_id as i32), + connection_id: source.connection_id.map(|id| id as i32), + definition: source.create_sql(), + acl: get_acl_items(&Object::SourceId(source.id), false, &users, username_map), + initialized_at: source.initialized_at_epoch.map(|e| e.as_timestamptz()), + created_at: source.created_at_epoch.map(|e| e.as_timestamptz()), + initialized_at_cluster_version: source.initialized_at_cluster_version.clone(), + created_at_cluster_version: source.created_at_cluster_version.clone(), + }) }) .collect()) } diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_worker_nodes.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_worker_nodes.rs index 226b0230e3f21..6c21f524e684e 100644 --- a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_worker_nodes.rs +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_worker_nodes.rs @@ -55,7 +55,7 @@ async fn read_rw_worker_nodes_info(reader: &SysCatalogReaderImpl) -> Result ExprImpl { + if let Some(GeneratedOrDefaultColumn::DefaultColumn(DefaultColumnDesc { expr, .. })) = self + .columns[col_idx] + .column_desc + .generated_or_default_column + .as_ref() + { + ExprImpl::from_expr_proto(expr.as_ref().unwrap()) + .expect("expr in default columns corrupted") + } else { + ExprImpl::literal_null(self.columns[col_idx].data_type().clone()) + } + } + pub fn default_columns(&self) -> impl Iterator + '_ { self.columns.iter().enumerate().filter_map(|(i, c)| { if let Some(GeneratedOrDefaultColumn::DefaultColumn(DefaultColumnDesc { @@ -479,6 +493,10 @@ impl TableCatalog { .collect(), ) } + + pub fn is_created(&self) -> bool { + self.stream_job_status == StreamJobStatus::Created + } } impl From for TableCatalog { @@ -581,19 +599,15 @@ impl OwnedByUserCatalog for TableCatalog { #[cfg(test)] mod tests { - use risingwave_common::catalog::{ - row_id_column_desc, ColumnCatalog, ColumnDesc, ColumnId, TableId, - }; + use risingwave_common::catalog::{row_id_column_desc, ColumnDesc, ColumnId}; use risingwave_common::test_prelude::*; use risingwave_common::types::*; use risingwave_common::util::sort_util::OrderType; - use risingwave_pb::catalog::{PbStreamJobStatus, PbTable}; use risingwave_pb::plan_common::{ AdditionalColumn, ColumnDescVersion, PbColumnCatalog, PbColumnDesc, }; use super::*; - use crate::catalog::table_catalog::{TableCatalog, TableType}; #[test] fn test_into_table_catalog() { diff --git a/src/frontend/src/error.rs b/src/frontend/src/error.rs index e2ec1b35fe504..93b3e627ae856 100644 --- a/src/frontend/src/error.rs +++ b/src/frontend/src/error.rs @@ -237,3 +237,13 @@ impl From for RwError { ErrorCode::Uncategorized(join_error.into()).into() } } + +// For errors without a concrete type, put them into `Uncategorized`. +impl From for RwError { + fn from(e: BoxedError) -> Self { + // Show that the error is of `BoxedKind`, instead of `AdhocKind` which loses the sources. + // This is essentially expanded from `anyhow::anyhow!(e)`. + let e = anyhow::__private::kind::BoxedKind::anyhow_kind(&e).new(e); + ErrorCode::Uncategorized(e).into() + } +} diff --git a/src/frontend/src/expr/agg_call.rs b/src/frontend/src/expr/agg_call.rs index 0f9493a694952..d72fba4dbcd2c 100644 --- a/src/frontend/src/expr/agg_call.rs +++ b/src/frontend/src/expr/agg_call.rs @@ -12,22 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + use risingwave_common::types::DataType; use risingwave_expr::aggregate::AggKind; use super::{infer_type, Expr, ExprImpl, Literal, OrderBy}; +use crate::catalog::function_catalog::{FunctionCatalog, FunctionKind}; use crate::error::Result; use crate::utils::Condition; #[derive(Clone, Eq, PartialEq, Hash)] pub struct AggCall { - agg_kind: AggKind, - return_type: DataType, - args: Vec, - distinct: bool, - order_by: OrderBy, - filter: Condition, - direct_args: Vec, + pub agg_kind: AggKind, + pub return_type: DataType, + pub args: Vec, + pub distinct: bool, + pub order_by: OrderBy, + pub filter: Condition, + pub direct_args: Vec, + /// Catalog of user defined aggregate function. + pub user_defined: Option>, } impl std::fmt::Debug for AggCall { @@ -69,6 +74,7 @@ impl AggCall { order_by, filter, direct_args, + user_defined: None, }) } @@ -86,27 +92,32 @@ impl AggCall { order_by: OrderBy::any(), filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }) } - pub fn decompose( - self, - ) -> ( - AggKind, - Vec, - bool, - OrderBy, - Condition, - Vec, - ) { - ( - self.agg_kind, - self.args, - self.distinct, - self.order_by, - self.filter, - self.direct_args, - ) + /// Create a user-defined `AggCall`. + pub fn new_user_defined( + args: Vec, + distinct: bool, + order_by: OrderBy, + filter: Condition, + direct_args: Vec, + user_defined: Arc, + ) -> Result { + let FunctionKind::Aggregate = &user_defined.kind else { + panic!("not an aggregate function"); + }; + Ok(AggCall { + agg_kind: AggKind::UserDefined, + return_type: user_defined.return_type.clone(), + args, + distinct, + order_by, + filter, + direct_args, + user_defined: Some(user_defined), + }) } pub fn agg_kind(&self) -> AggKind { diff --git a/src/frontend/src/expr/expr_rewriter.rs b/src/frontend/src/expr/expr_rewriter.rs index 48655430a0db5..6300f9d5e8858 100644 --- a/src/frontend/src/expr/expr_rewriter.rs +++ b/src/frontend/src/expr/expr_rewriter.rs @@ -12,33 +12,59 @@ // See the License for the specific language governing permissions and // limitations under the License. +use risingwave_common::util::recursive::{tracker, Recurse}; + use super::{ AggCall, CorrelatedInputRef, ExprImpl, FunctionCall, FunctionCallWithLambda, InputRef, Literal, - Parameter, Subquery, TableFunction, UserDefinedFunction, WindowFunction, + Parameter, Subquery, TableFunction, UserDefinedFunction, WindowFunction, EXPR_DEPTH_THRESHOLD, + EXPR_TOO_DEEP_NOTICE, }; use crate::expr::Now; +use crate::session::current::notice_to_user; + +/// The default implementation of [`ExprRewriter::rewrite_expr`] that simply dispatches to other +/// methods based on the type of the expression. +/// +/// You can use this function as a helper to reduce boilerplate code when implementing the trait. +// TODO: This is essentially a mimic of `super` pattern from OO languages. Ideally, we should +// adopt the style proposed in https://github.com/risingwavelabs/risingwave/issues/13477. +pub fn default_rewrite_expr( + rewriter: &mut R, + expr: ExprImpl, +) -> ExprImpl { + // TODO: Implementors may choose to not use this function at all, in which case we will fail + // to track the recursion and grow the stack as necessary. The current approach is only a + // best-effort attempt to prevent stack overflow. + tracker!().recurse(|t| { + if t.depth_reaches(EXPR_DEPTH_THRESHOLD) { + notice_to_user(EXPR_TOO_DEEP_NOTICE); + } + + match expr { + ExprImpl::InputRef(inner) => rewriter.rewrite_input_ref(*inner), + ExprImpl::Literal(inner) => rewriter.rewrite_literal(*inner), + ExprImpl::FunctionCall(inner) => rewriter.rewrite_function_call(*inner), + ExprImpl::FunctionCallWithLambda(inner) => { + rewriter.rewrite_function_call_with_lambda(*inner) + } + ExprImpl::AggCall(inner) => rewriter.rewrite_agg_call(*inner), + ExprImpl::Subquery(inner) => rewriter.rewrite_subquery(*inner), + ExprImpl::CorrelatedInputRef(inner) => rewriter.rewrite_correlated_input_ref(*inner), + ExprImpl::TableFunction(inner) => rewriter.rewrite_table_function(*inner), + ExprImpl::WindowFunction(inner) => rewriter.rewrite_window_function(*inner), + ExprImpl::UserDefinedFunction(inner) => rewriter.rewrite_user_defined_function(*inner), + ExprImpl::Parameter(inner) => rewriter.rewrite_parameter(*inner), + ExprImpl::Now(inner) => rewriter.rewrite_now(*inner), + } + }) +} /// By default, `ExprRewriter` simply traverses the expression tree and leaves nodes unchanged. /// Implementations can override a subset of methods and perform transformation on some particular /// types of expression. pub trait ExprRewriter { fn rewrite_expr(&mut self, expr: ExprImpl) -> ExprImpl { - match expr { - ExprImpl::InputRef(inner) => self.rewrite_input_ref(*inner), - ExprImpl::Literal(inner) => self.rewrite_literal(*inner), - ExprImpl::FunctionCall(inner) => self.rewrite_function_call(*inner), - ExprImpl::FunctionCallWithLambda(inner) => { - self.rewrite_function_call_with_lambda(*inner) - } - ExprImpl::AggCall(inner) => self.rewrite_agg_call(*inner), - ExprImpl::Subquery(inner) => self.rewrite_subquery(*inner), - ExprImpl::CorrelatedInputRef(inner) => self.rewrite_correlated_input_ref(*inner), - ExprImpl::TableFunction(inner) => self.rewrite_table_function(*inner), - ExprImpl::WindowFunction(inner) => self.rewrite_window_function(*inner), - ExprImpl::UserDefinedFunction(inner) => self.rewrite_user_defined_function(*inner), - ExprImpl::Parameter(inner) => self.rewrite_parameter(*inner), - ExprImpl::Now(inner) => self.rewrite_now(*inner), - } + default_rewrite_expr(self, expr) } fn rewrite_function_call(&mut self, func_call: FunctionCall) -> ExprImpl { @@ -60,16 +86,33 @@ pub trait ExprRewriter { } fn rewrite_agg_call(&mut self, agg_call: AggCall) -> ExprImpl { - let (func_type, inputs, distinct, order_by, filter, direct_args) = agg_call.decompose(); - let inputs = inputs + let AggCall { + agg_kind, + return_type, + args, + distinct, + order_by, + filter, + direct_args, + user_defined, + } = agg_call; + let args = args .into_iter() .map(|expr| self.rewrite_expr(expr)) .collect(); let order_by = order_by.rewrite_expr(self); let filter = filter.rewrite_expr(self); - AggCall::new(func_type, inputs, distinct, order_by, filter, direct_args) - .unwrap() - .into() + AggCall { + agg_kind, + return_type, + args, + distinct, + order_by, + filter, + direct_args, + user_defined, + } + .into() } fn rewrite_parameter(&mut self, parameter: Parameter) -> ExprImpl { @@ -97,7 +140,7 @@ pub trait ExprRewriter { args, return_type, function_type, - udtf_catalog, + user_defined: udtf_catalog, } = table_func; let args = args .into_iter() @@ -107,7 +150,7 @@ pub trait ExprRewriter { args, return_type, function_type, - udtf_catalog, + user_defined: udtf_catalog, } .into() } diff --git a/src/frontend/src/expr/expr_visitor.rs b/src/frontend/src/expr/expr_visitor.rs index 4e0484397ab9e..64b5c61b565dd 100644 --- a/src/frontend/src/expr/expr_visitor.rs +++ b/src/frontend/src/expr/expr_visitor.rs @@ -12,10 +12,48 @@ // See the License for the specific language governing permissions and // limitations under the License. +use risingwave_common::util::recursive::{tracker, Recurse}; + use super::{ AggCall, CorrelatedInputRef, ExprImpl, FunctionCall, FunctionCallWithLambda, InputRef, Literal, Now, Parameter, Subquery, TableFunction, UserDefinedFunction, WindowFunction, + EXPR_DEPTH_THRESHOLD, EXPR_TOO_DEEP_NOTICE, }; +use crate::session::current::notice_to_user; + +/// The default implementation of [`ExprVisitor::visit_expr`] that simply dispatches to other +/// methods based on the type of the expression. +/// +/// You can use this function as a helper to reduce boilerplate code when implementing the trait. +// TODO: This is essentially a mimic of `super` pattern from OO languages. Ideally, we should +// adopt the style proposed in https://github.com/risingwavelabs/risingwave/issues/13477. +pub fn default_visit_expr(visitor: &mut V, expr: &ExprImpl) { + // TODO: Implementors may choose to not use this function at all, in which case we will fail + // to track the recursion and grow the stack as necessary. The current approach is only a + // best-effort attempt to prevent stack overflow. + tracker!().recurse(|t| { + if t.depth_reaches(EXPR_DEPTH_THRESHOLD) { + notice_to_user(EXPR_TOO_DEEP_NOTICE); + } + + match expr { + ExprImpl::InputRef(inner) => visitor.visit_input_ref(inner), + ExprImpl::Literal(inner) => visitor.visit_literal(inner), + ExprImpl::FunctionCall(inner) => visitor.visit_function_call(inner), + ExprImpl::FunctionCallWithLambda(inner) => { + visitor.visit_function_call_with_lambda(inner) + } + ExprImpl::AggCall(inner) => visitor.visit_agg_call(inner), + ExprImpl::Subquery(inner) => visitor.visit_subquery(inner), + ExprImpl::CorrelatedInputRef(inner) => visitor.visit_correlated_input_ref(inner), + ExprImpl::TableFunction(inner) => visitor.visit_table_function(inner), + ExprImpl::WindowFunction(inner) => visitor.visit_window_function(inner), + ExprImpl::UserDefinedFunction(inner) => visitor.visit_user_defined_function(inner), + ExprImpl::Parameter(inner) => visitor.visit_parameter(inner), + ExprImpl::Now(inner) => visitor.visit_now(inner), + } + }) +} /// Traverse an expression tree. /// @@ -27,20 +65,7 @@ use super::{ /// subqueries are not traversed. pub trait ExprVisitor { fn visit_expr(&mut self, expr: &ExprImpl) { - match expr { - ExprImpl::InputRef(inner) => self.visit_input_ref(inner), - ExprImpl::Literal(inner) => self.visit_literal(inner), - ExprImpl::FunctionCall(inner) => self.visit_function_call(inner), - ExprImpl::FunctionCallWithLambda(inner) => self.visit_function_call_with_lambda(inner), - ExprImpl::AggCall(inner) => self.visit_agg_call(inner), - ExprImpl::Subquery(inner) => self.visit_subquery(inner), - ExprImpl::CorrelatedInputRef(inner) => self.visit_correlated_input_ref(inner), - ExprImpl::TableFunction(inner) => self.visit_table_function(inner), - ExprImpl::WindowFunction(inner) => self.visit_window_function(inner), - ExprImpl::UserDefinedFunction(inner) => self.visit_user_defined_function(inner), - ExprImpl::Parameter(inner) => self.visit_parameter(inner), - ExprImpl::Now(inner) => self.visit_now(inner), - } + default_visit_expr(self, expr) } fn visit_function_call(&mut self, func_call: &FunctionCall) { func_call diff --git a/src/frontend/src/expr/function_impl/cast_regclass.rs b/src/frontend/src/expr/function_impl/cast_regclass.rs index b1ec47a2d3508..c350d3984ab97 100644 --- a/src/frontend/src/expr/function_impl/cast_regclass.rs +++ b/src/frontend/src/expr/function_impl/cast_regclass.rs @@ -15,7 +15,6 @@ use risingwave_common::session_config::SearchPath; use risingwave_expr::{capture_context, function, ExprError}; use risingwave_sqlparser::parser::{Parser, ParserError}; -use risingwave_sqlparser::tokenizer::{Token, Tokenizer}; use thiserror::Error; use thiserror_ext::AsReport; @@ -63,7 +62,11 @@ fn resolve_regclass_inner( db_name: &str, class_name: &str, ) -> Result { - let obj = parse_object_name(class_name)?; + // We use the full parser here because this function needs to accept every legal way + // of identifying an object in PG SQL as a valid value for the varchar + // literal. For example: 'foo', 'public.foo', '"my table"', and + // '"my schema".foo' must all work as values passed pg_table_size. + let obj = Parser::parse_object_name_str(class_name)?; if obj.0.len() == 1 { let class_name = obj.0[0].real_value(); @@ -81,21 +84,6 @@ fn resolve_regclass_inner( } } -fn parse_object_name(name: &str) -> Result { - // We use the full parser here because this function needs to accept every legal way - // of identifying an object in PG SQL as a valid value for the varchar - // literal. For example: 'foo', 'public.foo', '"my table"', and - // '"my schema".foo' must all work as values passed pg_table_size. - let mut tokenizer = Tokenizer::new(name); - let tokens = tokenizer - .tokenize_with_location() - .map_err(ParserError::from)?; - let mut parser = Parser::new(tokens); - let object = parser.parse_object_name()?; - parser.expect_token(&Token::EOF)?; - Ok(object) -} - #[function("cast_regclass(varchar) -> int4")] fn cast_regclass(class_name: &str) -> Result { let oid = resolve_regclass_impl_captured(class_name)?; diff --git a/src/frontend/src/expr/function_impl/has_privilege.rs b/src/frontend/src/expr/function_impl/has_privilege.rs new file mode 100644 index 0000000000000..735a9dd1b6d92 --- /dev/null +++ b/src/frontend/src/expr/function_impl/has_privilege.rs @@ -0,0 +1,189 @@ +// Copyright 2024 RisingWave Labs +// +// 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::HashSet; + +use risingwave_expr::{capture_context, function, ExprError, Result}; +use risingwave_pb::user::grant_privilege::{Action, Object}; +use thiserror_ext::AsReport; + +use super::context::{CATALOG_READER, DB_NAME, USER_INFO_READER}; +use crate::catalog::CatalogReader; +use crate::user::user_service::UserInfoReader; + +#[inline(always)] +pub fn user_not_found_err(inner_err: &str) -> ExprError { + ExprError::InvalidParam { + name: "user", + reason: inner_err.into(), + } +} + +#[function("has_table_privilege(int4, int4, varchar) -> boolean")] +fn has_table_privilege(user_id: i32, table_oid: i32, privileges: &str) -> Result { + // does user have privilege for table + let user_name = get_user_name_by_id_captured(user_id)?; + has_table_privilege_1(user_name.as_str(), table_oid, privileges) +} + +#[function("has_table_privilege(varchar, int4, varchar) -> boolean")] +fn has_table_privilege_1(user_name: &str, table_oid: i32, privileges: &str) -> Result { + let allowed_actions = HashSet::new(); + let actions = parse_privilege(privileges, &allowed_actions)?; + // currently, we haven't support grant for view. + has_privilege_impl_captured( + user_name, + &get_grant_object_by_oid_captured(table_oid)?, + &actions, + ) +} + +#[function("has_any_column_privilege(int4, int4, varchar) -> boolean")] +fn has_any_column_privilege(user_id: i32, table_oid: i32, privileges: &str) -> Result { + // does user have privilege for any column of table + let user_name = get_user_name_by_id_captured(user_id)?; + has_any_column_privilege_1(user_name.as_str(), table_oid, privileges) +} + +#[function("has_any_column_privilege(varchar, int4, varchar) -> boolean")] +fn has_any_column_privilege_1(user_name: &str, table_oid: i32, privileges: &str) -> Result { + let allowed_actions = HashSet::from_iter([Action::Select, Action::Insert, Action::Update]); + let actions = parse_privilege(privileges, &allowed_actions)?; + has_privilege_impl_captured( + user_name, + &get_grant_object_by_oid_captured(table_oid)?, + &actions, + ) +} + +#[function("has_schema_privilege(varchar, int4, varchar) -> boolean")] +fn has_schema_privilege(user_name: &str, schema_oid: i32, privileges: &str) -> Result { + // does user have privilege for schema + let allowed_actions = HashSet::from_iter([Action::Create, Action::Usage]); + let actions = parse_privilege(privileges, &allowed_actions)?; + has_privilege_impl_captured(user_name, &Object::SchemaId(schema_oid as u32), &actions) +} + +#[function("has_schema_privilege(int4, varchar, varchar) -> boolean")] +fn has_schema_privilege_1(user_id: i32, schema_name: &str, privileges: &str) -> Result { + let user_name = get_user_name_by_id_captured(user_id)?; + let schema_oid = get_schema_id_by_name_captured(schema_name)?; + has_schema_privilege(user_name.as_str(), schema_oid, privileges) +} + +#[function("has_schema_privilege(int4, int4, varchar) -> boolean")] +fn has_schema_privilege_2(user_id: i32, schema_oid: i32, privileges: &str) -> Result { + let user_name = get_user_name_by_id_captured(user_id)?; + has_schema_privilege(user_name.as_str(), schema_oid, privileges) +} + +#[function("has_schema_privilege(varchar, varchar, varchar) -> boolean")] +fn has_schema_privilege_3(user_name: &str, schema_name: &str, privileges: &str) -> Result { + let schema_oid = get_schema_id_by_name_captured(schema_name)?; + has_schema_privilege(user_name, schema_oid, privileges) +} + +#[capture_context(USER_INFO_READER)] +fn has_privilege_impl( + user_info_reader: &UserInfoReader, + user_name: &str, + object: &Object, + actions: &Vec<(Action, bool)>, +) -> Result { + let user_info = &user_info_reader.read_guard(); + let user_catalog = user_info + .get_user_by_name(user_name) + .ok_or(user_not_found_err( + format!("User {} not found", user_name).as_str(), + ))?; + Ok(user_catalog.check_privilege_with_grant_option(object, actions)) +} + +#[capture_context(USER_INFO_READER)] +fn get_user_name_by_id(user_info_reader: &UserInfoReader, user_id: i32) -> Result { + let user_info = &user_info_reader.read_guard(); + user_info + .get_user_name_by_id(user_id as u32) + .ok_or(user_not_found_err( + format!("User {} not found", user_id).as_str(), + )) +} + +#[capture_context(CATALOG_READER, DB_NAME)] +fn get_grant_object_by_oid( + catalog_reader: &CatalogReader, + db_name: &str, + oid: i32, +) -> Result { + catalog_reader + .read_guard() + .get_database_by_name(db_name) + .map_err(|e| ExprError::InvalidParam { + name: "oid", + reason: e.to_report_string().into(), + })? + .get_grant_object_by_oid(oid as u32) + .ok_or(ExprError::InvalidParam { + name: "oid", + reason: format!("Table {} not found", oid).as_str().into(), + }) +} + +#[capture_context(CATALOG_READER, DB_NAME)] +fn get_schema_id_by_name( + catalog_reader: &CatalogReader, + db_name: &str, + schema_name: &str, +) -> Result { + let reader = &catalog_reader.read_guard(); + Ok(reader + .get_schema_by_name(db_name, schema_name) + .map_err(|e| ExprError::InvalidParam { + name: "schema", + reason: e.to_report_string().into(), + })? + .id() as i32) +} + +fn parse_privilege( + privilege_string: &str, + allowed_actions: &HashSet, +) -> Result> { + let mut privileges = Vec::new(); + for part in privilege_string.split(',').map(str::trim) { + let (privilege_type, grant_option) = match part.rsplit_once(" WITH GRANT OPTION") { + Some((p, _)) => (p, true), + None => (part, false), + }; + match Action::from_str_name(privilege_type.to_uppercase().as_str()) { + Some(Action::Unspecified) | None => { + return Err(ExprError::InvalidParam { + name: "privilege", + reason: format!("unrecognized privilege type: \"{}\"", part).into(), + }) + } + Some(action) => { + if allowed_actions.is_empty() || allowed_actions.contains(&action) { + privileges.push((action, grant_option)) + } else { + return Err(ExprError::InvalidParam { + name: "privilege", + reason: format!("unrecognized privilege type: \"{}\"", part).into(), + }); + } + } + } + } + Ok(privileges) +} diff --git a/src/frontend/src/expr/function_impl/mod.rs b/src/frontend/src/expr/function_impl/mod.rs index ad0b3b7a853d8..a0cff36840b42 100644 --- a/src/frontend/src/expr/function_impl/mod.rs +++ b/src/frontend/src/expr/function_impl/mod.rs @@ -15,8 +15,10 @@ mod cast_regclass; mod col_description; pub mod context; +mod has_privilege; mod pg_get_indexdef; mod pg_get_userbyid; mod pg_get_viewdef; +mod pg_index_column_has_property; mod pg_indexes_size; mod pg_relation_size; diff --git a/src/frontend/src/expr/function_impl/pg_get_viewdef.rs b/src/frontend/src/expr/function_impl/pg_get_viewdef.rs index d2e9caf9f6f29..fda4eb961501c 100644 --- a/src/frontend/src/expr/function_impl/pg_get_viewdef.rs +++ b/src/frontend/src/expr/function_impl/pg_get_viewdef.rs @@ -39,7 +39,7 @@ fn pg_get_viewdef_impl( if let Ok(view) = catalog_reader.get_view_by_id(db_name, oid as u32) { write!(writer, "{}", view.sql).unwrap(); Ok(()) - } else if let Ok(mv) = catalog_reader.get_table_by_id_with_db(db_name, oid as u32) { + } else if let Ok(mv) = catalog_reader.get_created_table_by_id_with_db(db_name, oid as u32) { let stmts = Parser::parse_sql(&mv.definition).map_err(|e| anyhow!(e))?; let [stmt]: [_; 1] = stmts.try_into().unwrap(); diff --git a/src/frontend/src/expr/function_impl/pg_index_column_has_property.rs b/src/frontend/src/expr/function_impl/pg_index_column_has_property.rs new file mode 100644 index 0000000000000..fdabf5aefc77e --- /dev/null +++ b/src/frontend/src/expr/function_impl/pg_index_column_has_property.rs @@ -0,0 +1,128 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_expr::{capture_context, function, Result}; + +use super::context::{CATALOG_READER, DB_NAME}; +use crate::catalog::CatalogReader; + +/// Tests whether an index column has the named property. +/// +/// `index` is the OID of the index. +/// `column` is the column number (1-based) within the index. +/// +/// NULL is returned if the property name is not known or does not apply to the particular object, +/// or if the OID or column number does not identify a valid object. +/// +/// # Supported Properties +/// +/// - `asc`: Does the column sort in ascending order on a forward scan? +/// - `desc`: Does the column sort in descending order on a forward scan? +/// - `nulls_first`: Does the column sort with nulls first on a forward scan? +/// - `nulls_last`: Does the column sort with nulls last on a forward scan? +/// +/// # Examples +/// +/// ```slt +/// statement ok +/// create table t(a int, b int); +/// +/// statement ok +/// create index i on t (a asc, b desc); +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 1, 'asc'); +/// ---- +/// t +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 1, 'DESC'); +/// ---- +/// f +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 1, 'nulls_FIRST'); +/// ---- +/// f +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 1, 'nulls_last'); +/// ---- +/// t +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 2, 'asc'); +/// ---- +/// f +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 2, 'desc'); +/// ---- +/// t +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 2, 'nulls_first'); +/// ---- +/// t +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 2, 'nulls_last'); +/// ---- +/// f +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 1, 'gg'); -- invalid property +/// ---- +/// NULL +/// +/// query B +/// select pg_index_column_has_property('i'::regclass, 0, 'asc'); -- column 0 does not exist +/// ---- +/// NULL +/// +/// statement ok +/// drop index i; +/// +/// statement ok +/// drop table t; +/// ``` +#[function("pg_index_column_has_property(int4, int4, varchar) -> boolean")] +fn pg_index_column_has_property(index: i32, column: i32, property: &str) -> Result> { + pg_index_column_has_property_impl_captured(index, column, property) +} + +#[capture_context(CATALOG_READER, DB_NAME)] +fn pg_index_column_has_property_impl( + catalog: &CatalogReader, + db_name: &str, + index_id: i32, + column_idx: i32, + property: &str, + // `Result` is not necessary for this function, but it's required by `capture_context`. +) -> Result> { + let catalog_reader = catalog.read_guard(); + let Ok(index) = catalog_reader.get_index_by_id(db_name, index_id as u32) else { + return Ok(None); + }; + let Some(properties) = index.get_column_properties((column_idx - 1) as usize) else { + return Ok(None); + }; + Ok(match property.to_lowercase().as_str() { + "asc" => Some(!properties.is_desc), + "desc" => Some(properties.is_desc), + "nulls_first" => Some(properties.nulls_first), + "nulls_last" => Some(!properties.nulls_first), + _ => None, + }) +} diff --git a/src/frontend/src/expr/mod.rs b/src/frontend/src/expr/mod.rs index 893ae425b8513..89142d0e9b237 100644 --- a/src/frontend/src/expr/mod.rs +++ b/src/frontend/src/expr/mod.rs @@ -53,8 +53,8 @@ mod utils; pub use agg_call::AggCall; pub use correlated_input_ref::{CorrelatedId, CorrelatedInputRef, Depth}; pub use expr_mutator::ExprMutator; -pub use expr_rewriter::ExprRewriter; -pub use expr_visitor::ExprVisitor; +pub use expr_rewriter::{default_rewrite_expr, ExprRewriter}; +pub use expr_visitor::{default_visit_expr, ExprVisitor}; pub use function_call::{is_row_function, FunctionCall, FunctionCallDisplay}; pub use function_call_with_lambda::FunctionCallWithLambda; pub use input_ref::{input_ref_to_column_indices, InputRef, InputRefDisplay}; @@ -74,6 +74,10 @@ pub use user_defined_function::UserDefinedFunction; pub use utils::*; pub use window_function::WindowFunction; +const EXPR_DEPTH_THRESHOLD: usize = 30; +const EXPR_TOO_DEEP_NOTICE: &str = "Some expression is too complicated. \ +Consider simplifying or splitting the query if you encounter any issues."; + /// the trait of bound expressions pub trait Expr: Into { /// Get the return type of the expr @@ -202,6 +206,20 @@ impl ExprImpl { .into() } + /// Create a new expression by merging the given expressions by `And`. + /// + /// If `exprs` is empty, return a literal `true`. + pub fn and(exprs: impl IntoIterator) -> Self { + merge_expr_by_logical(exprs, ExprType::And, ExprImpl::literal_bool(true)) + } + + /// Create a new expression by merging the given expressions by `Or`. + /// + /// If `exprs` is empty, return a literal `false`. + pub fn or(exprs: impl IntoIterator) -> Self { + merge_expr_by_logical(exprs, ExprType::Or, ExprImpl::literal_bool(false)) + } + /// Collect all `InputRef`s' indexes in the expression. /// /// # Panics @@ -395,7 +413,7 @@ macro_rules! impl_has_variant { }; } -impl_has_variant! {InputRef, Literal, FunctionCall, FunctionCallWithLambda, AggCall, Subquery, TableFunction, WindowFunction} +impl_has_variant! {InputRef, Literal, FunctionCall, FunctionCallWithLambda, AggCall, Subquery, TableFunction, WindowFunction, Now} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InequalityInputPair { @@ -647,10 +665,11 @@ impl ExprImpl { fn is_short_circuit(&self, func_call: &FunctionCall) -> bool { /// evaluate the first parameter of `Or` or `And` function call fn eval_first(e: &ExprImpl, expect: bool) -> bool { - let Some(Ok(Some(scalar))) = e.try_fold_const() else { - return false; - }; - scalar == ScalarImpl::Bool(expect) + if let ExprImpl::Literal(l) = e { + *l.get_data() == Some(ScalarImpl::Bool(expect)) + } else { + false + } } match func_call.func_type { @@ -1035,11 +1054,7 @@ impl ExprImpl { impl From for ExprImpl { fn from(c: Condition) -> Self { - merge_expr_by_binary( - c.conjunctions.into_iter(), - ExprType::And, - ExprImpl::literal_bool(true), - ) + ExprImpl::and(c.conjunctions) } } diff --git a/src/frontend/src/expr/now.rs b/src/frontend/src/expr/now.rs index a918a2b123573..fceece1301758 100644 --- a/src/frontend/src/expr/now.rs +++ b/src/frontend/src/expr/now.rs @@ -83,6 +83,19 @@ impl ExprRewriter for InlineNowProcTime { } } +/// Expression rewriter to rewrite `NOW()` to `PROCTIME()` +/// +/// This is applied for the sink into table query for those column with default expression containing `now()` because streaming execution can not handle `now` expression +pub struct RewriteNowToProcTime; + +impl ExprRewriter for RewriteNowToProcTime { + fn rewrite_now(&mut self, _now: Now) -> ExprImpl { + FunctionCall::new(expr_node::Type::Proctime, vec![]) + .unwrap() + .into() + } +} + #[derive(Default)] pub struct NowProcTimeFinder { has: bool, diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index bc18959e5be55..fe87eb6c2280c 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -209,6 +209,7 @@ impl ExprVisitor for ImpureAnalyzer { | Type::JsonbPathMatch | Type::JsonbPathQueryArray | Type::JsonbPathQueryFirst + | Type::JsonbSet | Type::IsJson | Type::ToJsonb | Type::Sind @@ -245,7 +246,9 @@ impl ExprVisitor for ImpureAnalyzer { | Type::ConvertTo | Type::IcebergTransform | Type::InetNtoa - | Type::InetAton => + | Type::InetAton + | Type::QuoteLiteral + | Type::QuoteNullable => // expression output is deterministic(same result for the same input) { func_call @@ -255,6 +258,7 @@ impl ExprVisitor for ImpureAnalyzer { } // expression output is not deterministic Type::Vnode + | Type::TestPaidTier | Type::Proctime | Type::PgSleep | Type::PgSleepFor @@ -267,6 +271,10 @@ impl ExprVisitor for ImpureAnalyzer { | Type::PgIndexesSize | Type::PgRelationSize | Type::PgGetSerialSequence + | Type::PgIndexColumnHasProperty + | Type::HasTablePrivilege + | Type::HasAnyColumnPrivilege + | Type::HasSchemaPrivilege | Type::MakeTimestamptz => self.impure = true, } } diff --git a/src/frontend/src/expr/table_function.rs b/src/frontend/src/expr/table_function.rs index 4703112bc16d3..0db14d4736c2d 100644 --- a/src/frontend/src/expr/table_function.rs +++ b/src/frontend/src/expr/table_function.rs @@ -17,9 +17,7 @@ use std::sync::Arc; use itertools::Itertools; use risingwave_common::types::DataType; pub use risingwave_pb::expr::table_function::PbType as TableFunctionType; -use risingwave_pb::expr::{ - TableFunction as TableFunctionPb, UserDefinedTableFunction as UserDefinedTableFunctionPb, -}; +use risingwave_pb::expr::PbTableFunction; use super::{infer_type, Expr, ExprImpl, ExprRewriter, RwResult}; use crate::catalog::function_catalog::{FunctionCatalog, FunctionKind}; @@ -35,7 +33,7 @@ pub struct TableFunction { pub return_type: DataType, pub function_type: TableFunctionType, /// Catalog of user defined table function. - pub udtf_catalog: Option>, + pub user_defined: Option>, } impl TableFunction { @@ -47,7 +45,7 @@ impl TableFunction { args, return_type, function_type: func_type, - udtf_catalog: None, + user_defined: None, }) } @@ -59,37 +57,24 @@ impl TableFunction { TableFunction { args, return_type: catalog.return_type.clone(), - function_type: TableFunctionType::Udtf, - udtf_catalog: Some(catalog), + function_type: TableFunctionType::UserDefined, + user_defined: Some(catalog), } } - pub fn to_protobuf(&self) -> TableFunctionPb { - TableFunctionPb { + pub fn to_protobuf(&self) -> PbTableFunction { + PbTableFunction { function_type: self.function_type as i32, args: self.args.iter().map(|c| c.to_expr_proto()).collect_vec(), return_type: Some(self.return_type.to_protobuf()), - udtf: self - .udtf_catalog - .as_ref() - .map(|c| UserDefinedTableFunctionPb { - arg_names: c.arg_names.clone(), - arg_types: c.arg_types.iter().map(|t| t.to_protobuf()).collect(), - language: c.language.clone(), - link: c.link.clone(), - identifier: c.identifier.clone(), - body: c.body.clone(), - compressed_binary: c.compressed_binary.clone(), - function_type: c.function_type.clone(), - runtime: c.runtime.clone(), - }), + udf: self.user_defined.as_ref().map(|c| c.as_ref().into()), } } /// Get the name of the table function. pub fn name(&self) -> String { match self.function_type { - TableFunctionType::Udtf => self.udtf_catalog.as_ref().unwrap().name.clone(), + TableFunctionType::UserDefined => self.user_defined.as_ref().unwrap().name.clone(), t => t.as_str_name().to_lowercase(), } } diff --git a/src/frontend/src/expr/type_inference/func.rs b/src/frontend/src/expr/type_inference/func.rs index 9f28dfeb74c8c..0fecee8ab45c0 100644 --- a/src/frontend/src/expr/type_inference/func.rs +++ b/src/frontend/src/expr/type_inference/func.rs @@ -20,6 +20,7 @@ use risingwave_common::types::{DataType, StructType}; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_expr::aggregate::AggKind; pub use risingwave_expr::sig::*; +use risingwave_pb::expr::table_function::PbType as PbTableFuncType; use super::{align_types, cast_ok_base, CastContext}; use crate::error::{ErrorCode, Result}; @@ -36,13 +37,24 @@ pub fn infer_type_with_sigmap( sig_map: &FunctionRegistry, ) -> Result { // special cases - if let FuncName::Scalar(func_type) = func_name - && let Some(res) = infer_type_for_special(func_type, inputs).transpose() - { - return res; - } - if let FuncName::Aggregate(AggKind::Grouping) = func_name { - return Ok(DataType::Int32); + match &func_name { + FuncName::Scalar(func_type) => { + if let Some(res) = infer_type_for_special(*func_type, inputs).transpose() { + return res; + } + } + FuncName::Table(func_type) => { + if let Some(res) = infer_type_for_special_table_function(*func_type, inputs).transpose() + { + return res; + } + } + FuncName::Aggregate(agg_kind) => { + if *agg_kind == AggKind::Grouping { + return Ok(DataType::Int32); + } + } + _ => {} } let actuals = inputs @@ -634,6 +646,34 @@ fn infer_type_for_special( } } +fn infer_type_for_special_table_function( + func_type: PbTableFuncType, + inputs: &mut [ExprImpl], +) -> Result> { + match func_type { + PbTableFuncType::GenerateSeries => { + if inputs.len() < 3 { + // let signature map handle this + return Ok(None); + } + match ( + inputs[0].return_type(), + inputs[1].return_type(), + inputs[2].return_type(), + ) { + (DataType::Timestamptz, DataType::Timestamptz, DataType::Interval) => { + // This is to allow `generate_series('2024-06-20 00:00:00'::timestamptz, now(), interval '1 day')`, + // which in streaming mode will be further converted to `StreamNow`. + Ok(Some(DataType::Timestamptz)) + } + // let signature map handle the rest + _ => Ok(None), + } + } + _ => Ok(None), + } +} + /// From all available functions in `sig_map`, find and return the best matching `FuncSign` for the /// provided `func_name` and `inputs`. This not only support exact function signature match, but can /// also match `substr(varchar, smallint)` or even `substr(varchar, unknown)` to `substr(varchar, diff --git a/src/frontend/src/expr/utils.rs b/src/frontend/src/expr/utils.rs index 54d0521b3f8ef..cc49f3c215378 100644 --- a/src/frontend/src/expr/utils.rs +++ b/src/frontend/src/expr/utils.rs @@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::VecDeque; + use fixedbitset::FixedBitSet; use risingwave_common::types::{DataType, ScalarImpl}; use risingwave_pb::expr::expr_node::Type; +use super::now::RewriteNowToProcTime; use super::{Expr, ExprImpl, ExprRewriter, ExprVisitor, FunctionCall, InputRef}; use crate::expr::ExprType; @@ -31,19 +34,30 @@ fn split_expr_by(expr: ExprImpl, op: ExprType, rets: &mut Vec) { } } -pub fn merge_expr_by_binary(mut exprs: I, op: ExprType, identity_elem: ExprImpl) -> ExprImpl +/// Merge the given expressions by the logical operation. +/// +/// The `op` must be commutative and associative, typically `And` or `Or`. +pub(super) fn merge_expr_by_logical(exprs: I, op: ExprType, identity_elem: ExprImpl) -> ExprImpl where - I: Iterator, + I: IntoIterator, { - if let Some(e) = exprs.next() { - let mut ret = e; - for expr in exprs { - ret = FunctionCall::new(op, vec![ret, expr]).unwrap().into(); + let mut exprs: VecDeque<_> = exprs.into_iter().map(|e| (0usize, e)).collect(); + + while exprs.len() > 1 { + let (level, lhs) = exprs.pop_front().unwrap(); + let rhs_level = exprs.front().unwrap().0; + + // If there's one element left in the current level, move it to the end of the next level. + if level < rhs_level { + exprs.push_back((level, lhs)); + } else { + let rhs = exprs.pop_front().unwrap().1; + let new_expr = FunctionCall::new(op, vec![lhs, rhs]).unwrap().into(); + exprs.push_back((level + 1, new_expr)); } - ret - } else { - identity_elem } + + exprs.pop_front().map(|(_, e)| e).unwrap_or(identity_elem) } /// Transform a bool expression to Conjunctive form. e.g. given expression is @@ -393,17 +407,7 @@ pub fn factorization_expr(expr: ExprImpl) -> Vec { disjunction.retain(|factor| !greatest_common_divider.contains(factor)); } // now disjunctions == [[A, B], [B], [E]] - let remaining = merge_expr_by_binary( - disjunctions.into_iter().map(|conjunction| { - merge_expr_by_binary( - conjunction.into_iter(), - ExprType::And, - ExprImpl::literal_bool(true), - ) - }), - ExprType::Or, - ExprImpl::literal_bool(false), - ); + let remaining = ExprImpl::or(disjunctions.into_iter().map(ExprImpl::and)); // now remaining is (A & B) | (B) | (E) // the result is C & D & ((A & B) | (B) | (E)) greatest_common_divider @@ -499,6 +503,11 @@ impl ExprVisitor for CountNow { } } +pub fn rewrite_now_to_proctime(expr: ExprImpl) -> ExprImpl { + let mut r = RewriteNowToProcTime; + r.rewrite_expr(expr) +} + /// analyze if the expression can derive a watermark from some input watermark. If it can /// derive, return the input watermark column index pub fn try_derive_watermark(expr: &ExprImpl) -> WatermarkDerivation { diff --git a/src/frontend/src/handler/alter_owner.rs b/src/frontend/src/handler/alter_owner.rs index 74207ad60cad0..29c003b5c1613 100644 --- a/src/frontend/src/handler/alter_owner.rs +++ b/src/frontend/src/handler/alter_owner.rs @@ -76,8 +76,11 @@ pub async fn handle_alter_owner( ( match stmt_type { StatementType::ALTER_TABLE | StatementType::ALTER_MATERIALIZED_VIEW => { - let (table, schema_name) = - catalog_reader.get_table_by_name(db_name, schema_path, &real_obj_name)?; + let (table, schema_name) = catalog_reader.get_created_table_by_name( + db_name, + schema_path, + &real_obj_name, + )?; session.check_privilege_for_drop_alter(schema_name, &**table)?; let schema_id = catalog_reader .get_schema_by_name(db_name, schema_name)? diff --git a/src/frontend/src/handler/alter_parallelism.rs b/src/frontend/src/handler/alter_parallelism.rs index b21e36481e20e..e5d2d3303789f 100644 --- a/src/frontend/src/handler/alter_parallelism.rs +++ b/src/frontend/src/handler/alter_parallelism.rs @@ -52,7 +52,7 @@ pub async fn handle_alter_parallelism( | StatementType::ALTER_MATERIALIZED_VIEW | StatementType::ALTER_INDEX => { let (table, schema_name) = - reader.get_table_by_name(db_name, schema_path, &real_table_name)?; + reader.get_created_table_by_name(db_name, schema_path, &real_table_name)?; match (table.table_type(), stmt_type) { (TableType::Internal, _) => { @@ -85,13 +85,6 @@ pub async fn handle_alter_parallelism( session.check_privilege_for_drop_alter(schema_name, &**sink)?; sink.id.sink_id() } - StatementType::ALTER_SUBSCRIPTION => { - let (subscription, schema_name) = - reader.get_subscription_by_name(db_name, schema_path, &real_table_name)?; - - session.check_privilege_for_drop_alter(schema_name, &**subscription)?; - subscription.id.subscription_id() - } _ => bail!( "invalid statement type for alter parallelism: {:?}", stmt_type diff --git a/src/frontend/src/handler/alter_rename.rs b/src/frontend/src/handler/alter_rename.rs index 8683d02e44ce1..ba9b647c2fd7e 100644 --- a/src/frontend/src/handler/alter_rename.rs +++ b/src/frontend/src/handler/alter_rename.rs @@ -43,7 +43,7 @@ pub async fn handle_rename_table( let table_id = { let reader = session.env().catalog_reader().read_guard(); let (table, schema_name) = - reader.get_table_by_name(db_name, schema_path, &real_table_name)?; + reader.get_created_table_by_name(db_name, schema_path, &real_table_name)?; if table_type != table.table_type { return Err(ErrorCode::InvalidInputSyntax(format!( "\"{table_name}\" is not a {}", @@ -332,7 +332,6 @@ pub async fn handle_rename_database( #[cfg(test)] mod tests { - use risingwave_common::catalog::{DEFAULT_DATABASE_NAME, DEFAULT_SCHEMA_NAME}; use crate::catalog::root_catalog::SchemaPath; @@ -350,7 +349,7 @@ mod tests { let table_id = { let catalog_reader = session.env().catalog_reader().read_guard(); catalog_reader - .get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t") + .get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t") .unwrap() .0 .id @@ -362,7 +361,7 @@ mod tests { let catalog_reader = session.env().catalog_reader().read_guard(); let altered_table_name = catalog_reader - .get_table_by_id(&table_id) + .get_any_table_by_id(&table_id) .unwrap() .name() .to_string(); diff --git a/src/frontend/src/handler/alter_set_schema.rs b/src/frontend/src/handler/alter_set_schema.rs index 4d9a0260450a8..2edad7adab61f 100644 --- a/src/frontend/src/handler/alter_set_schema.rs +++ b/src/frontend/src/handler/alter_set_schema.rs @@ -48,8 +48,11 @@ pub async fn handle_alter_set_schema( match stmt_type { StatementType::ALTER_TABLE | StatementType::ALTER_MATERIALIZED_VIEW => { - let (table, old_schema_name) = - catalog_reader.get_table_by_name(db_name, schema_path, &real_obj_name)?; + let (table, old_schema_name) = catalog_reader.get_created_table_by_name( + db_name, + schema_path, + &real_obj_name, + )?; if old_schema_name == new_schema_name { return Ok(RwPgResponse::empty_result(stmt_type)); } @@ -209,7 +212,7 @@ pub mod tests { let catalog_reader = session.env().catalog_reader().read_guard(); let schema_path = SchemaPath::Name(schema_name); catalog_reader - .get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "test_table") + .get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "test_table") .unwrap() .0 .clone() diff --git a/src/frontend/src/handler/alter_source_column.rs b/src/frontend/src/handler/alter_source_column.rs index fcabedc1149c4..43985518fd1b3 100644 --- a/src/frontend/src/handler/alter_source_column.rs +++ b/src/frontend/src/handler/alter_source_column.rs @@ -14,7 +14,7 @@ use itertools::Itertools; use pgwire::pg_response::{PgResponse, StatementType}; -use risingwave_common::catalog::ColumnId; +use risingwave_common::catalog::max_column_id; use risingwave_connector::source::{extract_source_struct, SourceEncode, SourceStruct}; use risingwave_sqlparser::ast::{ AlterSourceOperation, ColumnDef, CreateSourceStatement, ObjectName, Statement, @@ -106,10 +106,7 @@ pub async fn handle_alter_source_column( catalog.definition = alter_definition_add_column(&catalog.definition, column_def.clone())?; let mut bound_column = bind_sql_columns(&[column_def])?.remove(0); - bound_column.column_desc.column_id = columns - .iter() - .fold(ColumnId::new(i32::MIN), |a, b| a.max(b.column_id())) - .next(); + bound_column.column_desc.column_id = max_column_id(columns).next(); columns.push(bound_column); } _ => unreachable!(), diff --git a/src/frontend/src/handler/alter_source_with_sr.rs b/src/frontend/src/handler/alter_source_with_sr.rs index 8bd15fd3c01d4..840205caeadd3 100644 --- a/src/frontend/src/handler/alter_source_with_sr.rs +++ b/src/frontend/src/handler/alter_source_with_sr.rs @@ -18,7 +18,7 @@ use anyhow::Context; use itertools::Itertools; use pgwire::pg_response::StatementType; use risingwave_common::bail_not_implemented; -use risingwave_common::catalog::ColumnCatalog; +use risingwave_common::catalog::{max_column_id, ColumnCatalog}; use risingwave_connector::WithPropertiesExt; use risingwave_pb::catalog::StreamSourceInfo; use risingwave_pb::plan_common::{EncodeType, FormatType}; @@ -65,17 +65,23 @@ fn encode_type_to_encode(from: EncodeType) -> Option { EncodeType::Template => Encode::Template, EncodeType::Parquet => Encode::Parquet, EncodeType::None => Encode::None, + EncodeType::Text => Encode::Text, }) } -/// Returns the columns in `columns_a` but not in `columns_b`, -/// where the comparison is done by name and data type, -/// and hidden columns are ignored. +/// Returns the columns in `columns_a` but not in `columns_b`. +/// +/// Note: +/// - The comparison is done by name and data type, without checking `ColumnId`. +/// - Hidden columns and `INCLUDE ... AS ...` columns are ignored. Because it's only for the special handling of alter sr. +/// For the newly resolved `columns_from_resolve_source` (created by [`bind_columns_from_source`]), it doesn't contain hidden columns (`_row_id`) and `INCLUDE ... AS ...` columns. +/// This is fragile and we should really refactor it later. fn columns_minus(columns_a: &[ColumnCatalog], columns_b: &[ColumnCatalog]) -> Vec { columns_a .iter() .filter(|col_a| { !col_a.is_hidden() + && !col_a.is_connector_additional_column() && !columns_b.iter().any(|col_b| { col_a.name() == col_b.name() && col_a.data_type() == col_b.data_type() }) @@ -148,11 +154,7 @@ pub async fn refresh_sr_and_get_columns_diff( connector_schema: &ConnectorSchema, session: &Arc, ) -> Result<(StreamSourceInfo, Vec, Vec)> { - let mut with_properties = original_source - .with_properties - .clone() - .into_iter() - .collect(); + let mut with_properties = original_source.with_properties.clone(); validate_compatibility(connector_schema, &mut with_properties)?; if with_properties.is_cdc_connector() { @@ -166,8 +168,20 @@ pub async fn refresh_sr_and_get_columns_diff( unreachable!("source without schema registry is rejected") }; - let added_columns = columns_minus(&columns_from_resolve_source, &original_source.columns); + let mut added_columns = columns_minus(&columns_from_resolve_source, &original_source.columns); + // The newly resolved columns' column IDs also starts from 1. They cannot be used directly. + let mut next_col_id = max_column_id(&original_source.columns).next(); + for col in &mut added_columns { + col.column_desc.column_id = next_col_id; + next_col_id = next_col_id.next(); + } let dropped_columns = columns_minus(&original_source.columns, &columns_from_resolve_source); + tracing::debug!( + ?added_columns, + ?dropped_columns, + ?columns_from_resolve_source, + original_source = ?original_source.columns + ); Ok((source_info, added_columns, dropped_columns)) } diff --git a/src/frontend/src/handler/alter_streaming_rate_limit.rs b/src/frontend/src/handler/alter_streaming_rate_limit.rs new file mode 100644 index 0000000000000..e5273b85d57b8 --- /dev/null +++ b/src/frontend/src/handler/alter_streaming_rate_limit.rs @@ -0,0 +1,89 @@ +// Copyright 2024 RisingWave Labs +// +// 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 pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::bail; +use risingwave_pb::meta::PbThrottleTarget; +use risingwave_sqlparser::ast::ObjectName; + +use super::{HandlerArgs, RwPgResponse}; +use crate::catalog::root_catalog::SchemaPath; +use crate::catalog::table_catalog::TableType; +use crate::error::{ErrorCode, Result}; +use crate::Binder; + +pub async fn handle_alter_streaming_rate_limit( + handler_args: HandlerArgs, + kind: PbThrottleTarget, + table_name: ObjectName, + rate_limit: i32, +) -> Result { + let session = handler_args.session; + let db_name = session.database(); + let (schema_name, real_table_name) = + Binder::resolve_schema_qualified_name(db_name, table_name.clone())?; + let search_path = session.config().search_path(); + let user_name = &session.auth_context().user_name; + + let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name); + + let (stmt_type, id) = match kind { + PbThrottleTarget::Mv => { + let reader = session.env().catalog_reader().read_guard(); + let (table, schema_name) = + reader.get_created_table_by_name(db_name, schema_path, &real_table_name)?; + if table.table_type != TableType::MaterializedView { + return Err(ErrorCode::InvalidInputSyntax(format!( + "\"{table_name}\" is not a materialized view", + )) + .into()); + } + session.check_privilege_for_drop_alter(schema_name, &**table)?; + (StatementType::ALTER_MATERIALIZED_VIEW, table.id.table_id) + } + PbThrottleTarget::Source => { + let reader = session.env().catalog_reader().read_guard(); + let (source, schema_name) = + reader.get_source_by_name(db_name, schema_path, &real_table_name)?; + session.check_privilege_for_drop_alter(schema_name, &**source)?; + (StatementType::ALTER_SOURCE, source.id) + } + PbThrottleTarget::TableWithSource => { + let reader = session.env().catalog_reader().read_guard(); + let (table, schema_name) = + reader.get_created_table_by_name(db_name, schema_path, &real_table_name)?; + session.check_privilege_for_drop_alter(schema_name, &**table)?; + // Get the corresponding source catalog. + let source_id = if let Some(id) = table.associated_source_id { + id.table_id() + } else { + bail!("ALTER STREAMING_RATE_LIMIT is not for table without source") + }; + (StatementType::ALTER_SOURCE, source_id) + } + _ => bail!("Unsupported throttle target: {:?}", kind), + }; + + let meta_client = session.env().meta_client(); + + let rate_limit = if rate_limit < 0 { + None + } else { + Some(rate_limit as u32) + }; + + meta_client.apply_throttle(kind, id, rate_limit).await?; + + Ok(PgResponse::empty_result(stmt_type)) +} diff --git a/src/frontend/src/handler/alter_table_column.rs b/src/frontend/src/handler/alter_table_column.rs index 0143a0e8367a3..0ddeb2d1e3d37 100644 --- a/src/frontend/src/handler/alter_table_column.rs +++ b/src/frontend/src/handler/alter_table_column.rs @@ -52,13 +52,14 @@ pub async fn replace_table_with_definition( on_conflict, with_version_column, wildcard_idx, + cdc_table_info, .. } = definition else { panic!("unexpected statement type: {:?}", definition); }; - let (graph, table, source) = generate_stream_graph_for_table( + let (graph, table, source, job_type) = generate_stream_graph_for_table( session, table_name, original_catalog, @@ -72,6 +73,7 @@ pub async fn replace_table_with_definition( append_only, on_conflict, with_version_column, + cdc_table_info, ) .await?; @@ -92,7 +94,7 @@ pub async fn replace_table_with_definition( let catalog_writer = session.catalog_writer()?; catalog_writer - .replace_table(source, table, graph, col_index_mapping) + .replace_table(source, table, graph, col_index_mapping, job_type) .await?; Ok(()) } @@ -145,6 +147,13 @@ pub async fn handle_alter_table_column( } } + if columns.is_empty() { + Err(ErrorCode::NotSupported( + "alter a table with empty column definitions".to_string(), + "Please recreate the table with column definitions.".to_string(), + ))? + } + match operation { AlterTableOperation::AddColumn { column_def: new_column, @@ -171,7 +180,7 @@ pub async fn handle_alter_table_column( ))? } - // Add the new column to the table definition. + // Add the new column to the table definition if it is not created by `create table (*)` syntax. columns.push(new_column); } @@ -210,7 +219,7 @@ pub async fn handle_alter_table_column( } _ => unreachable!(), - } + }; replace_table_with_definition( &session, @@ -250,7 +259,7 @@ pub fn fetch_table_catalog_for_alter( let original_catalog = { let reader = session.env().catalog_reader().read_guard(); let (table, schema_name) = - reader.get_table_by_name(db_name, schema_path, &real_table_name)?; + reader.get_created_table_by_name(db_name, schema_path, &real_table_name)?; match table.table_type() { TableType::Table => {} @@ -290,7 +299,7 @@ mod tests { let get_table = || { let catalog_reader = session.env().catalog_reader().read_guard(); catalog_reader - .get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t") + .get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t") .unwrap() .0 .clone() diff --git a/src/frontend/src/handler/create_aggregate.rs b/src/frontend/src/handler/create_aggregate.rs new file mode 100644 index 0000000000000..b9b8a391eaffe --- /dev/null +++ b/src/frontend/src/handler/create_aggregate.rs @@ -0,0 +1,141 @@ +// Copyright 2024 RisingWave Labs +// +// 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 anyhow::Context; +use risingwave_common::catalog::FunctionId; +use risingwave_expr::sig::{CreateFunctionOptions, UdfKind}; +use risingwave_pb::catalog::function::{AggregateFunction, Kind}; +use risingwave_pb::catalog::Function; +use risingwave_sqlparser::ast::DataType as AstDataType; + +use super::*; +use crate::catalog::CatalogError; +use crate::{bind_data_type, Binder}; + +pub async fn handle_create_aggregate( + handler_args: HandlerArgs, + or_replace: bool, + name: ObjectName, + args: Vec, + returns: AstDataType, + params: CreateFunctionBody, +) -> Result { + if or_replace { + bail_not_implemented!("CREATE OR REPLACE AGGREGATE"); + } + // e.g., `language [ python / java / ...etc]` + let language = match params.language { + Some(lang) => { + let lang = lang.real_value().to_lowercase(); + match &*lang { + "python" | "javascript" => lang, + _ => { + return Err(ErrorCode::InvalidParameterValue(format!( + "language {} is not supported", + lang + )) + .into()) + } + } + } + None => return Err(ErrorCode::InvalidParameterValue("no language".into()).into()), + }; + + let return_type = bind_data_type(&returns)?; + + let mut arg_names = vec![]; + let mut arg_types = vec![]; + for arg in args { + arg_names.push(arg.name.map_or("".to_string(), |n| n.real_value())); + arg_types.push(bind_data_type(&arg.data_type)?); + } + + // resolve database and schema id + let session = &handler_args.session; + let db_name = session.database(); + let (schema_name, function_name) = Binder::resolve_schema_qualified_name(db_name, name)?; + let (database_id, schema_id) = session.get_database_and_schema_id_for_create(schema_name)?; + + // check if the function exists in the catalog + if (session.env().catalog_reader().read_guard()) + .get_schema_by_id(&database_id, &schema_id)? + .get_function_by_name_args(&function_name, &arg_types) + .is_some() + { + let name = format!( + "{function_name}({})", + arg_types.iter().map(|t| t.to_string()).join(",") + ); + return Err(CatalogError::Duplicated("function", name).into()); + } + + let link = match ¶ms.using { + Some(CreateFunctionUsing::Link(l)) => Some(l.as_str()), + _ => None, + }; + let base64_decoded = match ¶ms.using { + Some(CreateFunctionUsing::Base64(encoded)) => { + use base64::prelude::{Engine, BASE64_STANDARD}; + let bytes = BASE64_STANDARD + .decode(encoded) + .context("invalid base64 encoding")?; + Some(bytes) + } + _ => None, + }; + let function_type = match params.function_type { + Some(CreateFunctionType::Sync) => Some("sync".to_string()), + Some(CreateFunctionType::Async) => Some("async".to_string()), + Some(CreateFunctionType::Generator) => Some("generator".to_string()), + Some(CreateFunctionType::AsyncGenerator) => Some("async_generator".to_string()), + None => None, + }; + + let create_fn = risingwave_expr::sig::find_udf_impl(&language, None, link)?.create_fn; + let output = create_fn(CreateFunctionOptions { + kind: UdfKind::Aggregate, + name: &function_name, + arg_names: &arg_names, + arg_types: &arg_types, + return_type: &return_type, + as_: params.as_.as_ref().map(|s| s.as_str()), + using_link: link, + using_base64_decoded: base64_decoded.as_deref(), + })?; + + let function = Function { + id: FunctionId::placeholder().0, + schema_id, + database_id, + name: function_name, + kind: Some(Kind::Aggregate(AggregateFunction {})), + arg_names, + arg_types: arg_types.into_iter().map(|t| t.into()).collect(), + return_type: Some(return_type.into()), + language, + identifier: Some(output.identifier), + link: link.map(|s| s.to_string()), + body: output.body, + compressed_binary: output.compressed_binary, + owner: session.user_id(), + always_retry_on_network_error: false, + runtime: None, + function_type, + }; + + let catalog_writer = session.catalog_writer()?; + catalog_writer.create_function(function).await?; + + Ok(PgResponse::empty_result(StatementType::CREATE_AGGREGATE)) +} diff --git a/src/frontend/src/handler/create_connection.rs b/src/frontend/src/handler/create_connection.rs index 22491f9cb0ee3..987f0e9fdd897 100644 --- a/src/frontend/src/handler/create_connection.rs +++ b/src/frontend/src/handler/create_connection.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use pgwire::pg_response::{PgResponse, StatementType}; use risingwave_connector::source::kafka::PRIVATELINK_CONNECTION; @@ -36,7 +36,7 @@ pub(crate) const CLOUD_PROVIDER_AWS: &str = "aws"; #[inline(always)] fn get_connection_property_required( - with_properties: &HashMap, + with_properties: &BTreeMap, property: &str, ) -> Result { with_properties @@ -50,7 +50,7 @@ fn get_connection_property_required( } fn resolve_private_link_properties( - with_properties: &HashMap, + with_properties: &BTreeMap, ) -> Result { let provider = match get_connection_property_required(with_properties, CONNECTION_PROVIDER_PROP)?.as_str() @@ -86,7 +86,7 @@ fn resolve_private_link_properties( } fn resolve_create_connection_payload( - with_properties: &HashMap, + with_properties: &BTreeMap, ) -> Result { let connection_type = get_connection_property_required(with_properties, CONNECTION_TYPE_PROP)?; let create_connection_payload = match connection_type.as_str() { diff --git a/src/frontend/src/handler/create_function.rs b/src/frontend/src/handler/create_function.rs index 428a5612e1770..ccd83a13ed81c 100644 --- a/src/frontend/src/handler/create_function.rs +++ b/src/frontend/src/handler/create_function.rs @@ -12,18 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use anyhow::{anyhow, Context}; -use arrow_schema::Fields; -use bytes::Bytes; -use itertools::Itertools; -use pgwire::pg_response::StatementType; +use anyhow::Context; use risingwave_common::catalog::FunctionId; use risingwave_common::types::DataType; -use risingwave_expr::expr::get_or_create_wasm_runtime; +use risingwave_expr::sig::{CreateFunctionOptions, UdfKind}; use risingwave_pb::catalog::function::{Kind, ScalarFunction, TableFunction}; use risingwave_pb::catalog::Function; -use risingwave_sqlparser::ast::{CreateFunctionBody, ObjectName, OperateFunctionArg}; -use risingwave_udf::ArrowFlightUdfClient; use super::*; use crate::catalog::CatalogError; @@ -65,10 +59,10 @@ pub async fn handle_create_function( None => "".to_string(), }; - let rt = match params.runtime { + let runtime = match params.runtime { Some(runtime) => { - if language.as_str() == "javascript" { - runtime.to_string() + if language == "javascript" { + Some(runtime.real_value()) } else { return Err(ErrorCode::InvalidParameterValue( "runtime is only supported for javascript".to_string(), @@ -76,7 +70,7 @@ pub async fn handle_create_function( .into()); } } - None => "".to_string(), + None => None, }; let return_type; @@ -133,194 +127,45 @@ pub async fn handle_create_function( return Err(CatalogError::Duplicated("function", name).into()); } - let identifier; - let mut link = None; - let mut body = None; - let mut compressed_binary = None; - let mut function_type = None; - let mut runtime = None; - - match language.as_str() { - "python" if params.using.is_none() => { - identifier = function_name.to_string(); - body = Some( - params - .as_ - .ok_or_else(|| ErrorCode::InvalidParameterValue("AS must be specified".into()))? - .into_string(), - ); - } - "python" | "java" | "" => { - let Some(CreateFunctionUsing::Link(l)) = params.using else { - return Err(ErrorCode::InvalidParameterValue( - "USING LINK must be specified".to_string(), - ) - .into()); - }; - let Some(as_) = params.as_ else { - return Err( - ErrorCode::InvalidParameterValue("AS must be specified".to_string()).into(), - ); - }; - identifier = as_.into_string(); - - // check UDF server - { - let client = ArrowFlightUdfClient::connect(&l) - .await - .map_err(|e| anyhow!(e))?; - /// A helper function to create a unnamed field from data type. - fn to_field(data_type: arrow_schema::DataType) -> arrow_schema::Field { - arrow_schema::Field::new("", data_type, true) - } - let args = arrow_schema::Schema::new( - arg_types - .iter() - .map::, _>(|t| Ok(to_field(t.try_into()?))) - .try_collect::<_, Fields, _>()?, - ); - let returns = arrow_schema::Schema::new(match kind { - Kind::Scalar(_) => vec![to_field(return_type.clone().try_into()?)], - Kind::Table(_) => vec![ - arrow_schema::Field::new("row_index", arrow_schema::DataType::Int32, true), - to_field(return_type.clone().try_into()?), - ], - _ => unreachable!(), - }); - client - .check(&identifier, &args, &returns) - .await - .context("failed to check UDF signature")?; - } - link = Some(l); - } - "javascript" if rt.as_str() != "deno" => { - identifier = function_name.to_string(); - body = Some( - params - .as_ - .ok_or_else(|| ErrorCode::InvalidParameterValue("AS must be specified".into()))? - .into_string(), - ); - runtime = Some("quickjs".to_string()); - } - "javascript" if rt.as_str() == "deno" => { - identifier = function_name.to_string(); - match (params.using, params.as_) { - (None, None) => { - return Err(ErrorCode::InvalidParameterValue( - "Either USING or AS must be specified".into(), - ) - .into()) - } - (None, Some(_as)) => body = Some(_as.into_string()), - (Some(CreateFunctionUsing::Link(link)), None) => { - let bytes = download_code_from_link(&link).await?; - compressed_binary = Some(zstd::stream::encode_all(bytes.as_slice(), 0)?); - } - (Some(CreateFunctionUsing::Base64(encoded)), None) => { - use base64::prelude::{Engine, BASE64_STANDARD}; - let bytes = BASE64_STANDARD - .decode(encoded) - .context("invalid base64 encoding")?; - compressed_binary = Some(zstd::stream::encode_all(bytes.as_slice(), 0)?); - } - (Some(_), Some(_)) => { - return Err(ErrorCode::InvalidParameterValue( - "Both USING and AS cannot be specified".into(), - ) - .into()) - } - }; - - function_type = match params.function_type { - Some(CreateFunctionType::Sync) => Some("sync".to_string()), - Some(CreateFunctionType::Async) => Some("async".to_string()), - Some(CreateFunctionType::Generator) => Some("generator".to_string()), - Some(CreateFunctionType::AsyncGenerator) => Some("async_generator".to_string()), - None => None, - }; - - runtime = Some("deno".to_string()); - } - "rust" => { - if params.using.is_some() { - return Err(ErrorCode::InvalidParameterValue( - "USING is not supported for rust function".to_string(), - ) - .into()); - } - let identifier_v1 = wasm_identifier_v1( - &function_name, - &arg_types, - &return_type, - matches!(kind, Kind::Table(_)), - ); - // if the function returns a struct, users need to add `#[function]` macro by themselves. - // otherwise, we add it automatically. the code should start with `fn ...`. - let function_macro = if return_type.is_struct() { - String::new() - } else { - format!("#[function(\"{}\")]", identifier_v1) - }; - let script = params - .as_ - .ok_or_else(|| ErrorCode::InvalidParameterValue("AS must be specified".into()))? - .into_string(); - let script = format!( - "use arrow_udf::{{function, types::*}};\n{}\n{}", - function_macro, script - ); - body = Some(script.clone()); - - let wasm_binary = tokio::task::spawn_blocking(move || { - let mut opts = arrow_udf_wasm::build::BuildOpts::default(); - opts.script = script; - // use a fixed tempdir to reuse the build cache - opts.tempdir = Some(std::env::temp_dir().join("risingwave-rust-udf")); - - arrow_udf_wasm::build::build_with(&opts) - }) - .await? - .context("failed to build rust function")?; - - let runtime = get_or_create_wasm_runtime(&wasm_binary)?; - identifier = find_wasm_identifier_v2(&runtime, &identifier_v1)?; - - compressed_binary = Some(zstd::stream::encode_all(wasm_binary.as_slice(), 0)?); - } - "wasm" => { - let Some(using) = params.using else { - return Err(ErrorCode::InvalidParameterValue( - "USING must be specified".to_string(), - ) - .into()); - }; - let wasm_binary = match using { - CreateFunctionUsing::Link(link) => download_binary_from_link(&link).await?, - CreateFunctionUsing::Base64(encoded) => { - // decode wasm binary from base64 - use base64::prelude::{Engine, BASE64_STANDARD}; - BASE64_STANDARD - .decode(encoded) - .context("invalid base64 encoding")? - .into() - } - }; - let runtime = get_or_create_wasm_runtime(&wasm_binary)?; - let identifier_v1 = wasm_identifier_v1( - &function_name, - &arg_types, - &return_type, - matches!(kind, Kind::Table(_)), - ); - identifier = find_wasm_identifier_v2(&runtime, &identifier_v1)?; - - compressed_binary = Some(zstd::stream::encode_all(wasm_binary.as_ref(), 0)?); + let link = match ¶ms.using { + Some(CreateFunctionUsing::Link(l)) => Some(l.as_str()), + _ => None, + }; + let base64_decoded = match ¶ms.using { + Some(CreateFunctionUsing::Base64(encoded)) => { + use base64::prelude::{Engine, BASE64_STANDARD}; + let bytes = BASE64_STANDARD + .decode(encoded) + .context("invalid base64 encoding")?; + Some(bytes) } - _ => unreachable!("invalid language: {language}"), + _ => None, + }; + let function_type = match params.function_type { + Some(CreateFunctionType::Sync) => Some("sync".to_string()), + Some(CreateFunctionType::Async) => Some("async".to_string()), + Some(CreateFunctionType::Generator) => Some("generator".to_string()), + Some(CreateFunctionType::AsyncGenerator) => Some("async_generator".to_string()), + None => None, }; + let create_fn = + risingwave_expr::sig::find_udf_impl(&language, runtime.as_deref(), link)?.create_fn; + let output = create_fn(CreateFunctionOptions { + kind: match kind { + Kind::Scalar(_) => UdfKind::Scalar, + Kind::Table(_) => UdfKind::Table, + Kind::Aggregate(_) => unreachable!(), + }, + name: &function_name, + arg_names: &arg_names, + arg_types: &arg_types, + return_type: &return_type, + as_: params.as_.as_ref().map(|s| s.as_str()), + using_link: link, + using_base64_decoded: base64_decoded.as_deref(), + })?; + let function = Function { id: FunctionId::placeholder().0, schema_id, @@ -331,10 +176,10 @@ pub async fn handle_create_function( arg_types: arg_types.into_iter().map(|t| t.into()).collect(), return_type: Some(return_type.into()), language, - identifier: Some(identifier), - link, - body, - compressed_binary, + identifier: Some(output.identifier), + link: link.map(|s| s.to_string()), + body: output.body, + compressed_binary: output.compressed_binary, owner: session.user_id(), always_retry_on_network_error: with_options .always_retry_on_network_error @@ -348,136 +193,3 @@ pub async fn handle_create_function( Ok(PgResponse::empty_result(StatementType::CREATE_FUNCTION)) } - -/// Download wasm binary from a link. -#[allow(clippy::unused_async)] -async fn download_binary_from_link(link: &str) -> Result { - // currently only local file system is supported - if let Some(path) = link.strip_prefix("fs://") { - let content = - std::fs::read(path).context("failed to read wasm binary from local file system")?; - Ok(content.into()) - } else { - Err(ErrorCode::InvalidParameterValue("only 'fs://' is supported".to_string()).into()) - } -} - -/// Convert a v0.1 function identifier to v0.2 format. -/// -/// In arrow-udf v0.1 format, struct type is inline in the identifier. e.g. -/// -/// ```text -/// keyvalue(varchar,varchar)->struct -/// ``` -/// -/// However, since arrow-udf v0.2, struct type is no longer inline. -/// The above identifier is divided into a function and a type. -/// -/// ```text -/// keyvalue(varchar,varchar)->struct KeyValue -/// KeyValue=key:varchar,value:varchar -/// ``` -/// -/// For compatibility, we should call `find_wasm_identifier_v2` to -/// convert v0.1 identifiers to v0.2 format before looking up the function. -fn find_wasm_identifier_v2( - runtime: &arrow_udf_wasm::Runtime, - inlined_signature: &str, -) -> Result { - // Inline types in function signature. - // - // # Example - // - // ```text - // types = { "KeyValue": "key:varchar,value:varchar" } - // input = "keyvalue(varchar, varchar) -> struct KeyValue" - // output = "keyvalue(varchar, varchar) -> struct" - // ``` - let inline_types = |s: &str| -> String { - let mut inlined = s.to_string(); - // iteratively replace `struct Xxx` with `struct<...>` until no replacement is made. - loop { - let replaced = inlined.clone(); - for (k, v) in runtime.types() { - inlined = inlined.replace(&format!("struct {k}"), &format!("struct<{v}>")); - } - if replaced == inlined { - return inlined; - } - } - }; - // Function signature in arrow-udf is case sensitive. - // However, SQL identifiers are usually case insensitive and stored in lowercase. - // So we should convert the signature to lowercase before comparison. - let identifier = runtime - .functions() - .find(|f| inline_types(f).to_lowercase() == inlined_signature) - .ok_or_else(|| { - ErrorCode::InvalidParameterValue(format!( - "function not found in wasm binary: \"{}\"\nHINT: available functions:\n {}\navailable types:\n {}", - inlined_signature, - runtime.functions().join("\n "), - runtime.types().map(|(k, v)| format!("{k}: {v}")).join("\n "), - )) - })?; - Ok(identifier.into()) -} - -/// Download wasm binary from a link. -#[allow(clippy::unused_async)] -async fn download_code_from_link(link: &str) -> Result> { - // currently only local file system is supported - if let Some(path) = link.strip_prefix("fs://") { - let content = - std::fs::read(path).context("failed to read the code from local file system")?; - Ok(content) - } else { - Err(ErrorCode::InvalidParameterValue("only 'fs://' is supported".to_string()).into()) - } -} - -/// Generate a function identifier in v0.1 format from the function signature. -fn wasm_identifier_v1( - name: &str, - args: &[DataType], - ret: &DataType, - table_function: bool, -) -> String { - format!( - "{}({}){}{}", - name, - args.iter().map(datatype_name).join(","), - if table_function { "->>" } else { "->" }, - datatype_name(ret) - ) -} - -/// Convert a data type to string used in identifier. -fn datatype_name(ty: &DataType) -> String { - match ty { - DataType::Boolean => "boolean".to_string(), - DataType::Int16 => "int2".to_string(), - DataType::Int32 => "int4".to_string(), - DataType::Int64 => "int8".to_string(), - DataType::Float32 => "float4".to_string(), - DataType::Float64 => "float8".to_string(), - DataType::Date => "date".to_string(), - DataType::Time => "time".to_string(), - DataType::Timestamp => "timestamp".to_string(), - DataType::Timestamptz => "timestamptz".to_string(), - DataType::Interval => "interval".to_string(), - DataType::Decimal => "decimal".to_string(), - DataType::Jsonb => "json".to_string(), - DataType::Serial => "serial".to_string(), - DataType::Int256 => "int256".to_string(), - DataType::Bytea => "bytea".to_string(), - DataType::Varchar => "varchar".to_string(), - DataType::List(inner) => format!("{}[]", datatype_name(inner)), - DataType::Struct(s) => format!( - "struct<{}>", - s.iter() - .map(|(name, ty)| format!("{}:{}", name, datatype_name(ty))) - .join(",") - ), - } -} diff --git a/src/frontend/src/handler/create_index.rs b/src/frontend/src/handler/create_index.rs index aeca32253f2c7..ee6429a85e32e 100644 --- a/src/frontend/src/handler/create_index.rs +++ b/src/frontend/src/handler/create_index.rs @@ -24,7 +24,7 @@ use pgwire::pg_response::{PgResponse, StatementType}; use risingwave_common::acl::AclMode; use risingwave_common::catalog::{IndexId, TableDesc, TableId}; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; -use risingwave_pb::catalog::{PbIndex, PbStreamJobStatus, PbTable}; +use risingwave_pb::catalog::{PbIndex, PbIndexColumnProperties, PbStreamJobStatus, PbTable}; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; use risingwave_pb::user::grant_privilege::Object; use risingwave_sqlparser::ast; @@ -61,7 +61,8 @@ pub(crate) fn resolve_index_schema( let catalog_reader = session.env().catalog_reader(); let read_guard = catalog_reader.read_guard(); - let (table, schema_name) = read_guard.get_table_by_name(db_name, schema_path, &table_name)?; + let (table, schema_name) = + read_guard.get_created_table_by_name(db_name, schema_path, &table_name)?; Ok((schema_name.to_string(), table.clone(), index_table_name)) } @@ -225,6 +226,13 @@ pub(crate) fn gen_create_index_plan( index_table_prost.dependent_relations = vec![table.id.table_id]; let index_columns_len = index_columns_ordered_expr.len() as u32; + let index_column_properties = index_columns_ordered_expr + .iter() + .map(|(_, order)| PbIndexColumnProperties { + is_desc: order.is_descending(), + nulls_first: order.nulls_are_first(), + }) + .collect(); let index_item = build_index_item( index_table.table_desc().into(), table.name(), @@ -241,6 +249,7 @@ pub(crate) fn gen_create_index_plan( index_table_id: TableId::placeholder().table_id, primary_table_id: table.id.table_id, index_item, + index_column_properties, index_columns_len, initialized_at_epoch: None, created_at_epoch: None, @@ -382,7 +391,7 @@ fn assemble_materialize( })) .collect_vec(); - PlanRoot::new( + PlanRoot::new_with_logical_plan( logical_project, // schema of logical_project is such that index columns come first. // so we can use distributed_by_columns_len to represent distributed by columns indices. diff --git a/src/frontend/src/handler/create_mv.rs b/src/frontend/src/handler/create_mv.rs index 94ff4eebbc66e..ed316231e2ce3 100644 --- a/src/frontend/src/handler/create_mv.rs +++ b/src/frontend/src/handler/create_mv.rs @@ -16,9 +16,8 @@ use either::Either; use itertools::Itertools; use pgwire::pg_response::{PgResponse, StatementType}; use risingwave_common::acl::AclMode; -use risingwave_pb::catalog::{CreateType, PbTable}; +use risingwave_pb::catalog::PbTable; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; -use risingwave_pb::stream_plan::StreamScanType; use risingwave_sqlparser::ast::{EmitMode, Ident, ObjectName, Query}; use super::privilege::resolve_relation_privileges; @@ -37,21 +36,24 @@ use crate::scheduler::streaming_manager::CreatingStreamingJobInfo; use crate::session::SessionImpl; use crate::stream_fragmenter::build_graph; +pub(super) fn parse_column_names(columns: &[Ident]) -> Option> { + if columns.is_empty() { + None + } else { + Some(columns.iter().map(|v| v.real_value()).collect()) + } +} + +/// If columns is empty, it means that the user did not specify the column names. +/// In this case, we extract the column names from the query. +/// If columns is not empty, it means that user specify the column names and the user +/// should guarantee that the column names number are consistent with the query. pub(super) fn get_column_names( bound: &BoundQuery, session: &SessionImpl, columns: Vec, ) -> Result>> { - // If columns is empty, it means that the user did not specify the column names. - // In this case, we extract the column names from the query. - // If columns is not empty, it means that user specify the column names and the user - // should guarantee that the column names number are consistent with the query. - let col_names: Option> = if columns.is_empty() { - None - } else { - Some(columns.iter().map(|v| v.real_value()).collect()) - }; - + let col_names = parse_column_names(&columns); if let BoundSetExpr::Select(select) = &bound.body { // `InputRef`'s alias will be implicitly assigned in `bind_project`. // If user provide columns name (col_names.is_some()), we don't need alias. @@ -164,7 +166,7 @@ pub async fn handle_create_mv( return Ok(resp); } - let (mut table, graph, can_run_in_background) = { + let (table, graph) = { let context = OptimizerContext::from_handler_args(handler_args); if !context.with_options().is_empty() { // get other useful fields by `remove`, the logic here is to reject unknown options. @@ -183,21 +185,7 @@ It only indicates the physical clustering of the data, which may improve the per let (plan, table) = gen_create_mv_plan(&session, context.into(), query, name, columns, emit_mode)?; - // All leaf nodes must be stream table scan, no other scan operators support recovery. - fn plan_has_backfill_leaf_nodes(plan: &PlanRef) -> bool { - if plan.inputs().is_empty() { - if let Some(scan) = plan.as_stream_table_scan() { - scan.stream_scan_type() == StreamScanType::Backfill - || scan.stream_scan_type() == StreamScanType::ArrangementBackfill - } else { - false - } - } else { - assert!(!plan.inputs().is_empty()); - plan.inputs().iter().all(plan_has_backfill_leaf_nodes) - } - } - let can_run_in_background = plan_has_backfill_leaf_nodes(&plan); + let context = plan.plan_base().ctx().clone(); let mut graph = build_graph(plan)?; graph.parallelism = @@ -211,7 +199,7 @@ It only indicates the physical clustering of the data, which may improve the per let ctx = graph.ctx.as_mut().unwrap(); ctx.timezone = context.get_session_timezone(); - (table, graph, can_run_in_background) + (table, graph) }; // Ensure writes to `StreamJobTracker` are atomic. @@ -226,14 +214,6 @@ It only indicates the physical clustering of the data, which may improve the per table.name.clone(), )); - let run_in_background = session.config().background_ddl(); - let create_type = if run_in_background && can_run_in_background { - CreateType::Background - } else { - CreateType::Foreground - }; - table.create_type = create_type.into(); - let session = session.clone(); let catalog_writer = session.catalog_writer()?; catalog_writer @@ -297,7 +277,7 @@ pub mod tests { // Check table exists. let (table, _) = catalog_reader - .get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "mv1") + .get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "mv1") .unwrap(); assert_eq!(table.name(), "mv1"); diff --git a/src/frontend/src/handler/create_schema.rs b/src/frontend/src/handler/create_schema.rs index 806fbce8af61d..46972a2757e3e 100644 --- a/src/frontend/src/handler/create_schema.rs +++ b/src/frontend/src/handler/create_schema.rs @@ -29,6 +29,7 @@ pub async fn handle_create_schema( handler_args: HandlerArgs, schema_name: ObjectName, if_not_exist: bool, + user_specified: Option, ) -> Result { let session = handler_args.session; let database_name = session.database(); @@ -62,6 +63,19 @@ pub async fn handle_create_schema( (db.id(), db.owner()) }; + let schema_owner = if let Some(user_specified) = user_specified { + let user_specified = Binder::resolve_user_name(user_specified)?; + session + .env() + .user_info_reader() + .read_guard() + .get_user_by_name(&user_specified) + .map(|u| u.id) + .ok_or_else(|| CatalogError::NotFound("user", user_specified.to_string()))? + } else { + session.user_id() + }; + session.check_privileges(&[ObjectCheckItem::new( db_owner, AclMode::Create, @@ -70,7 +84,7 @@ pub async fn handle_create_schema( let catalog_writer = session.catalog_writer()?; catalog_writer - .create_schema(db_id, &schema_name, session.user_id()) + .create_schema(db_id, &schema_name, schema_owner) .await?; Ok(PgResponse::empty_result(StatementType::CREATE_SCHEMA)) } diff --git a/src/frontend/src/handler/create_secret.rs b/src/frontend/src/handler/create_secret.rs new file mode 100644 index 0000000000000..2e99f26e97cb8 --- /dev/null +++ b/src/frontend/src/handler/create_secret.rs @@ -0,0 +1,102 @@ +// Copyright 2024 RisingWave Labs +// +// 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 pgwire::pg_response::{PgResponse, StatementType}; +use prost::Message; +use risingwave_common::bail_not_implemented; +use risingwave_sqlparser::ast::{CreateSecretStatement, SqlOption, Value}; + +use crate::error::{ErrorCode, Result}; +use crate::handler::{HandlerArgs, RwPgResponse}; +use crate::{Binder, WithOptions}; + +const SECRET_BACKEND_KEY: &str = "backend"; + +const SECRET_BACKEND_META: &str = "meta"; +const SECRET_BACKEND_HASHICORP_VAULT: &str = "hashicorp_vault"; + +pub async fn handle_create_secret( + handler_args: HandlerArgs, + stmt: CreateSecretStatement, +) -> Result { + let session = handler_args.session.clone(); + let db_name = session.database(); + let (schema_name, connection_name) = + Binder::resolve_schema_qualified_name(db_name, stmt.secret_name.clone())?; + + if let Err(e) = session.check_secret_name_duplicated(stmt.secret_name.clone()) { + return if stmt.if_not_exists { + Ok(PgResponse::builder(StatementType::CREATE_SECRET) + .notice(format!("secret \"{}\" exists, skipping", connection_name)) + .into()) + } else { + Err(e) + }; + } + + // check if the secret backend is supported + let with_props = WithOptions::try_from(stmt.with_properties.0.as_ref() as &[SqlOption])?; + let secret_payload: Vec = { + if let Some(backend) = with_props.inner().get(SECRET_BACKEND_KEY) { + match backend.to_lowercase().as_ref() { + SECRET_BACKEND_META => { + let backend = risingwave_pb::secret::Secret { + secret_backend: Some(risingwave_pb::secret::secret::SecretBackend::Meta( + risingwave_pb::secret::SecretMetaBackend { value: vec![] }, + )), + }; + backend.encode_to_vec() + } + SECRET_BACKEND_HASHICORP_VAULT => { + if stmt.credential != Value::Null { + return Err(ErrorCode::InvalidParameterValue( + "credential must be null for hashicorp_vault backend".to_string(), + ) + .into()); + } + bail_not_implemented!("hashicorp_vault backend is not implemented yet") + } + _ => { + return Err(ErrorCode::InvalidParameterValue(format!( + "secret backend \"{}\" is not supported. Supported backends are: {}", + backend, + [SECRET_BACKEND_META, SECRET_BACKEND_HASHICORP_VAULT].join(",") + )) + .into()); + } + } + } else { + return Err(ErrorCode::InvalidParameterValue(format!( + "secret backend is not specified in with clause. Supported backends are: {}", + [SECRET_BACKEND_META, SECRET_BACKEND_HASHICORP_VAULT].join(",") + )) + .into()); + } + }; + + let (database_id, schema_id) = session.get_database_and_schema_id_for_create(schema_name)?; + + let catalog_writer = session.catalog_writer()?; + catalog_writer + .create_secret( + stmt.secret_name.real_value(), + database_id, + schema_id, + session.user_id(), + secret_payload, + ) + .await?; + + Ok(PgResponse::empty_result(StatementType::CREATE_SECRET)) +} diff --git a/src/frontend/src/handler/create_sink.rs b/src/frontend/src/handler/create_sink.rs index 167ae62341782..b2a65b8d09780 100644 --- a/src/frontend/src/handler/create_sink.rs +++ b/src/frontend/src/handler/create_sink.rs @@ -12,19 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::rc::Rc; use std::sync::{Arc, LazyLock}; use anyhow::Context; -use arrow_schema::DataType as ArrowDataType; +use arrow_schema_iceberg::DataType as ArrowDataType; use either::Either; use itertools::Itertools; use maplit::{convert_args, hashmap}; use pgwire::pg_response::{PgResponse, StatementType}; -use risingwave_common::catalog::{ConnectionId, DatabaseId, SchemaId, TableId, UserId}; -use risingwave_common::types::{DataType, Datum}; -use risingwave_common::util::value_encoding::DatumFromProtoExt; +use risingwave_common::array::arrow::IcebergArrowConvert; +use risingwave_common::catalog::{ConnectionId, DatabaseId, Schema, SchemaId, TableId, UserId}; +use risingwave_common::types::DataType; use risingwave_common::{bail, catalog}; use risingwave_connector::sink::catalog::{SinkCatalog, SinkFormatDesc, SinkType}; use risingwave_connector::sink::iceberg::{IcebergConfig, ICEBERG_SINK}; @@ -32,8 +32,7 @@ use risingwave_connector::sink::{ CONNECTOR_TYPE_KEY, SINK_TYPE_OPTION, SINK_USER_FORCE_APPEND_ONLY_OPTION, SINK_WITHOUT_BACKFILL, }; use risingwave_pb::catalog::{PbSource, Table}; -use risingwave_pb::ddl_service::ReplaceTablePlan; -use risingwave_pb::plan_common::column_desc::GeneratedOrDefaultColumn; +use risingwave_pb::ddl_service::{ReplaceTablePlan, TableJobType}; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::{DispatcherType, MergeNode, StreamFragmentGraph, StreamNode}; @@ -49,9 +48,11 @@ use super::RwPgResponse; use crate::binder::Binder; use crate::catalog::catalog_service::CatalogReadGuard; use crate::catalog::source_catalog::SourceCatalog; +use crate::catalog::view_catalog::ViewCatalog; use crate::error::{ErrorCode, Result, RwError}; -use crate::expr::{ExprImpl, InputRef, Literal}; +use crate::expr::{rewrite_now_to_proctime, ExprImpl, InputRef}; use crate::handler::alter_table_column::fetch_table_catalog_for_alter; +use crate::handler::create_mv::parse_column_names; use crate::handler::create_table::{generate_stream_graph_for_table, ColumnIdGenerator}; use crate::handler::privilege::resolve_query_privileges; use crate::handler::util::SourceSchemaCompatExt; @@ -63,7 +64,7 @@ use crate::optimizer::{OptimizerContext, OptimizerContextRef, PlanRef, RelationC use crate::scheduler::streaming_manager::CreatingStreamingJobInfo; use crate::session::SessionImpl; use crate::stream_fragmenter::build_graph; -use crate::utils::resolve_privatelink_in_with_option; +use crate::utils::{resolve_privatelink_in_with_option, resolve_secret_in_with_options}; use crate::{Explain, Planner, TableCatalog, WithOptions}; // used to store result of `gen_sink_plan` @@ -80,6 +81,7 @@ pub fn gen_sink_plan( stmt: CreateSinkStatement, partition_info: Option, ) -> Result { + let user_specified_columns = !stmt.columns.is_empty(); let db_name = session.database(); let (sink_schema_name, sink_table_name) = Binder::resolve_schema_qualified_name(db_name, stmt.sink_name.clone())?; @@ -118,8 +120,12 @@ pub fn gen_sink_plan( let check_items = resolve_query_privileges(&bound); session.check_privileges(&check_items)?; - // If column names not specified, use the name in materialized view. - let col_names = get_column_names(&bound, session, stmt.columns)?; + let col_names = if sink_into_table_name.is_some() { + parse_column_names(&stmt.columns) + } else { + // If column names not specified, use the name in the bound query, which is equal with the plan root's original field name. + get_column_names(&bound, session, stmt.columns)? + }; let mut with_options = context.with_options().clone(); @@ -140,6 +146,7 @@ pub fn gen_sink_plan( resolve_privatelink_in_with_option(&mut with_options, &sink_schema_name, session)?; conn_id.map(ConnectionId) }; + let secret_ref = resolve_secret_in_with_options(&mut with_options, session)?; let emit_on_window_close = stmt.emit_mode == Some(EmitMode::OnWindowClose); if emit_on_window_close { @@ -172,8 +179,8 @@ pub fn gen_sink_plan( }; let mut plan_root = Planner::new(context).plan_query(bound)?; - if let Some(col_names) = col_names { - plan_root.set_out_names(col_names)?; + if let Some(col_names) = &col_names { + plan_root.set_out_names(col_names.clone())?; }; let without_backfill = match with_options.remove(SINK_WITHOUT_BACKFILL) { @@ -196,6 +203,25 @@ pub fn gen_sink_plan( .map(|table_name| fetch_table_catalog_for_alter(session, table_name)) .transpose()?; + if let Some(target_table_catalog) = &target_table_catalog { + if let Some(col_names) = col_names { + let target_table_columns = target_table_catalog + .columns() + .iter() + .map(|c| c.name()) + .collect::>(); + for c in col_names { + if !target_table_columns.contains(c.as_str()) { + return Err(RwError::from(ErrorCode::BindError(format!( + "Column {} not found in table {}", + c, + target_table_catalog.name() + )))); + } + } + } + } + let target_table = target_table_catalog.as_ref().map(|catalog| catalog.id()); let sink_plan = plan_root.gen_sink_plan( @@ -230,6 +256,7 @@ pub fn gen_sink_plan( UserId::new(session.user_id()), connection_id, dependent_relations.into_iter().collect_vec(), + secret_ref, ); if let Some(table_catalog) = &target_table_catalog { @@ -251,7 +278,12 @@ pub fn gen_sink_plan( ))); } - let exprs = derive_default_column_project_for_sink(&sink_catalog, table_catalog)?; + let exprs = derive_default_column_project_for_sink( + &sink_catalog, + sink_plan.schema(), + table_catalog, + user_specified_columns, + )?; let logical_project = generic::Project::new(exprs, sink_plan); @@ -280,13 +312,13 @@ pub fn gen_sink_plan( pub async fn get_partition_compute_info( with_options: &WithOptions, ) -> Result> { - let properties = HashMap::from_iter(with_options.clone().into_inner().into_iter()); + let properties = with_options.clone().into_inner(); let Some(connector) = properties.get(UPSTREAM_SOURCE_KEY) else { return Ok(None); }; match connector.as_str() { ICEBERG_SINK => { - let iceberg_config = IcebergConfig::from_hashmap(properties)?; + let iceberg_config = IcebergConfig::from_btreemap(properties)?; get_partition_compute_info_for_iceberg(&iceberg_config).await } _ => Ok(None), @@ -351,14 +383,14 @@ async fn get_partition_compute_info_for_iceberg( }) .collect::>>()?; - let DataType::Struct(partition_type) = arrow_type.into() else { + let ArrowDataType::Struct(partition_type) = arrow_type else { return Err(RwError::from(ErrorCode::SinkError( "Partition type of iceberg should be a struct type".into(), ))); }; Ok(Some(PartitionComputeInfo::Iceberg(IcebergPartitionInfo { - partition_type, + partition_type: IcebergArrowConvert.struct_from_fields(&partition_type)?, partition_fields, }))) } @@ -434,6 +466,7 @@ pub async fn handle_create_sink( table: Some(table), fragment_graph: Some(graph), table_col_index_mapping: None, + job_type: TableJobType::General as _, }); } @@ -465,6 +498,7 @@ fn check_cycle_for_sink( let mut sinks = HashMap::new(); let mut sources = HashMap::new(); + let mut views = HashMap::new(); let db_name = session.database(); for schema in reader.iter_schemas(db_name)? { for sink in schema.iter_sink() { @@ -474,12 +508,17 @@ fn check_cycle_for_sink( for source in schema.iter_source() { sources.insert(source.id, source.as_ref()); } + + for view in schema.iter_view() { + views.insert(view.id, view.as_ref()); + } } struct Context<'a> { reader: &'a CatalogReadGuard, sink_index: &'a HashMap, source_index: &'a HashMap, + view_index: &'a HashMap, } impl Context<'_> { @@ -523,11 +562,13 @@ fn check_cycle_for_sink( path: &mut Vec, ) -> Result<()> { for table_id in dependent_jobs { - if let Ok(table) = self.reader.get_table_by_id(table_id) { + if let Ok(table) = self.reader.get_any_table_by_id(table_id) { path.push(table.name.clone()); self.visit_table(table.as_ref(), target_table_id, path)?; path.pop(); - } else if self.source_index.get(&table_id.table_id).is_some() { + } else if self.source_index.contains_key(&table_id.table_id) + || self.view_index.contains_key(&table_id.table_id) + { continue; } else { bail!("streaming job not found: {:?}", table_id); @@ -546,6 +587,7 @@ fn check_cycle_for_sink( reader: &reader, sink_index: &sinks, source_index: &sources, + view_index: &views, }; ctx.visit_dependent_jobs(&sink_catalog.dependent_relations, table_id, &mut path)?; @@ -593,7 +635,7 @@ pub(crate) async fn reparse_table_for_sink( panic!("unexpected statement type: {:?}", definition); }; - let (graph, table, source) = generate_stream_graph_for_table( + let (graph, table, source, _) = generate_stream_graph_for_table( session, table_name, table_catalog, @@ -607,6 +649,7 @@ pub(crate) async fn reparse_table_for_sink( append_only, on_conflict, with_version_column, + None, ) .await?; @@ -632,66 +675,81 @@ pub(crate) fn insert_merger_to_union(node: &mut StreamNode) { insert_merger_to_union(input); } } + +fn derive_sink_to_table_expr( + sink_schema: &Schema, + idx: usize, + target_type: &DataType, +) -> Result { + let input_type = &sink_schema.fields()[idx].data_type; + + if target_type != input_type { + bail!( + "column type mismatch: {:?} vs {:?}", + target_type, + input_type + ); + } else { + Ok(ExprImpl::InputRef(Box::new(InputRef::new( + idx, + input_type.clone(), + )))) + } +} + fn derive_default_column_project_for_sink( sink: &SinkCatalog, + sink_schema: &Schema, target_table_catalog: &Arc, + user_specified_columns: bool, ) -> Result> { + assert_eq!(sink.full_schema().len(), sink_schema.len()); + let mut exprs = vec![]; - let sink_visible_columns = sink + let sink_visible_col_idxes = sink .full_columns() .iter() - .enumerate() - .filter(|(_i, c)| !c.is_hidden()) + .positions(|c| !c.is_hidden()) .collect_vec(); + let sink_visible_col_idxes_by_name = sink + .full_columns() + .iter() + .enumerate() + .filter(|(_, c)| !c.is_hidden()) + .map(|(i, c)| (c.name(), i)) + .collect::>(); for (idx, table_column) in target_table_catalog.columns().iter().enumerate() { if table_column.is_generated() { continue; } - let data_type = table_column.data_type(); - - if idx < sink_visible_columns.len() { - let (sink_col_idx, sink_column) = sink_visible_columns[idx]; + let default_col_expr = || -> ExprImpl { + rewrite_now_to_proctime(target_table_catalog.default_column_expr(idx)) + }; - let sink_col_type = sink_column.data_type(); + let sink_col_expr = |sink_col_idx: usize| -> Result { + derive_sink_to_table_expr(sink_schema, sink_col_idx, table_column.data_type()) + }; - if data_type != sink_col_type { - bail!( - "column type mismatch: {:?} vs {:?}", - data_type, - sink_col_type - ); + // If users specified the columns to be inserted e.g. `CREATE SINK s INTO t(a, b)`, the expressions of `Project` will be generated accordingly. + // The missing columns will be filled with default value (`null` if not explicitly defined). + // Otherwise, e.g. `CREATE SINK s INTO t`, the columns will be matched by their order in `select` query and the target table. + #[allow(clippy::collapsible_else_if)] + if user_specified_columns { + if let Some(idx) = sink_visible_col_idxes_by_name.get(table_column.name()) { + exprs.push(sink_col_expr(*idx)?); } else { - exprs.push(ExprImpl::InputRef(Box::new(InputRef::new( - sink_col_idx, - data_type.clone(), - )))); + exprs.push(default_col_expr()); } } else { - let data = match table_column - .column_desc - .generated_or_default_column - .as_ref() - { - // default column with default value - Some(GeneratedOrDefaultColumn::DefaultColumn(default_column)) => { - Datum::from_protobuf(default_column.get_snapshot_value().unwrap(), data_type) - .unwrap() - } - // default column with no default value - None => None, - - // generated column is unreachable - _ => unreachable!(), + if idx < sink_visible_col_idxes.len() { + exprs.push(sink_col_expr(sink_visible_col_idxes[idx])?); + } else { + exprs.push(default_col_expr()); }; - - exprs.push(ExprImpl::Literal(Box::new(Literal::new( - data, - data_type.clone(), - )))); - }; + } } Ok(exprs) } @@ -717,10 +775,23 @@ fn bind_sink_format_desc(value: ConnectorSchema) -> Result { E::Protobuf => SinkEncode::Protobuf, E::Avro => SinkEncode::Avro, E::Template => SinkEncode::Template, - e @ (E::Native | E::Csv | E::Bytes | E::None | E::Parquet) => { + e @ (E::Native | E::Csv | E::Bytes | E::None | E::Text | E::Parquet) => { return Err(ErrorCode::BindError(format!("sink encode unsupported: {e}")).into()); } }; + + let mut key_encode = None; + if let Some(encode) = value.key_encode { + if encode == E::Text { + key_encode = Some(SinkEncode::Text); + } else { + return Err(ErrorCode::BindError(format!( + "sink key encode unsupported: {encode}, only TEXT supported" + )) + .into()); + } + } + let mut options = WithOptions::try_from(value.row_options.as_slice())?.into_inner(); options @@ -731,11 +802,13 @@ fn bind_sink_format_desc(value: ConnectorSchema) -> Result { format, encode, options, + key_encode, }) } static CONNECTORS_COMPATIBLE_FORMATS: LazyLock>>> = LazyLock::new(|| { + use risingwave_connector::sink::google_pubsub::GooglePubSubSink; use risingwave_connector::sink::kafka::KafkaSink; use risingwave_connector::sink::kinesis::KinesisSink; use risingwave_connector::sink::mqtt::MqttSink; @@ -744,8 +817,11 @@ static CONNECTORS_COMPATIBLE_FORMATS: LazyLock hashmap!( + Format::Plain => vec![Encode::Json], + ), KafkaSink::SINK_NAME => hashmap!( - Format::Plain => vec![Encode::Json, Encode::Protobuf], + Format::Plain => vec![Encode::Json, Encode::Avro, Encode::Protobuf], Format::Upsert => vec![Encode::Json, Encode::Avro], Format::Debezium => vec![Encode::Json], ), @@ -763,8 +839,8 @@ static CONNECTORS_COMPATIBLE_FORMATS: LazyLock vec![Encode::Json], ), RedisSink::SINK_NAME => hashmap!( - Format::Plain => vec![Encode::Json,Encode::Template], - Format::Upsert => vec![Encode::Json,Encode::Template], + Format::Plain => vec![Encode::Json, Encode::Template], + Format::Upsert => vec![Encode::Json, Encode::Template], ), )) }); @@ -848,7 +924,7 @@ pub mod tests { // Check table exists. let (table, schema_name) = catalog_reader - .get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "mv1") + .get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "mv1") .unwrap(); assert_eq!(table.name(), "mv1"); diff --git a/src/frontend/src/handler/create_source.rs b/src/frontend/src/handler/create_source.rs index c7551804bb1d5..9a9b5cfdee219 100644 --- a/src/frontend/src/handler/create_source.rs +++ b/src/frontend/src/handler/create_source.rs @@ -21,18 +21,19 @@ use either::Either; use itertools::Itertools; use maplit::{convert_args, hashmap}; use pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::array::arrow::IcebergArrowConvert; use risingwave_common::bail_not_implemented; use risingwave_common::catalog::{ - is_column_ids_dedup, ColumnCatalog, ColumnDesc, ColumnId, Schema, TableId, + debug_assert_column_ids_distinct, ColumnCatalog, ColumnDesc, ColumnId, Schema, TableId, INITIAL_SOURCE_VERSION_ID, KAFKA_TIMESTAMP_COLUMN_NAME, }; use risingwave_common::types::DataType; use risingwave_connector::parser::additional_columns::{ - build_additional_column_catalog, COMPATIBLE_ADDITIONAL_COLUMNS, + build_additional_column_desc, get_supported_additional_columns, }; use risingwave_connector::parser::{ - schema_to_columns, AvroParserConfig, DebeziumAvroParserConfig, ProtobufParserConfig, - SpecificParserConfig, TimestamptzHandling, DEBEZIUM_IGNORE_KEY, + fetch_json_schema_and_map_to_columns, AvroParserConfig, DebeziumAvroParserConfig, + ProtobufParserConfig, SpecificParserConfig, TimestamptzHandling, DEBEZIUM_IGNORE_KEY, }; use risingwave_connector::schema::schema_registry::{ name_strategy_from_str, SchemaRegistryAuth, SCHEMA_REGISTRY_PASSWORD, SCHEMA_REGISTRY_USERNAME, @@ -40,7 +41,8 @@ use risingwave_connector::schema::schema_registry::{ use risingwave_connector::sink::iceberg::IcebergConfig; use risingwave_connector::source::cdc::{ CDC_SHARING_MODE_KEY, CDC_SNAPSHOT_BACKFILL, CDC_SNAPSHOT_MODE_KEY, CDC_TRANSACTIONAL_KEY, - CITUS_CDC_CONNECTOR, MONGODB_CDC_CONNECTOR, MYSQL_CDC_CONNECTOR, POSTGRES_CDC_CONNECTOR, + CDC_WAIT_FOR_STREAMING_START_TIMEOUT, CITUS_CDC_CONNECTOR, MONGODB_CDC_CONNECTOR, + MYSQL_CDC_CONNECTOR, POSTGRES_CDC_CONNECTOR, }; use risingwave_connector::source::datagen::DATAGEN_CONNECTOR; use risingwave_connector::source::iceberg::ICEBERG_CONNECTOR; @@ -52,15 +54,13 @@ use risingwave_connector::source::{ POSIX_FS_CONNECTOR, PULSAR_CONNECTOR, S3_CONNECTOR, }; use risingwave_connector::WithPropertiesExt; -use risingwave_pb::catalog::{ - PbSchemaRegistryNameStrategy, PbSource, StreamSourceInfo, WatermarkDesc, -}; +use risingwave_pb::catalog::{PbSchemaRegistryNameStrategy, StreamSourceInfo, WatermarkDesc}; use risingwave_pb::plan_common::additional_column::ColumnType as AdditionalColumnType; use risingwave_pb::plan_common::{EncodeType, FormatType}; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; use risingwave_sqlparser::ast::{ get_delimiter, AstString, ColumnDef, ConnectorSchema, CreateSourceStatement, Encode, Format, - ProtobufSchema, SourceWatermark, + ObjectName, ProtobufSchema, SourceWatermark, TableConstraint, }; use risingwave_sqlparser::parser::IncludeOption; use thiserror_ext::AsReport; @@ -68,19 +68,20 @@ use thiserror_ext::AsReport; use super::RwPgResponse; use crate::binder::Binder; use crate::catalog::source_catalog::SourceCatalog; +use crate::catalog::{DatabaseId, SchemaId}; use crate::error::ErrorCode::{self, Deprecated, InvalidInputSyntax, NotSupported, ProtocolError}; use crate::error::{Result, RwError}; use crate::expr::Expr; use crate::handler::create_table::{ - bind_pk_on_relation, bind_sql_column_constraints, bind_sql_columns, bind_sql_pk_names, - ensure_table_constraints_supported, ColumnIdGenerator, + bind_pk_and_row_id_on_relation, bind_sql_column_constraints, bind_sql_columns, + bind_sql_pk_names, ensure_table_constraints_supported, ColumnIdGenerator, }; use crate::handler::util::SourceSchemaCompatExt; use crate::handler::HandlerArgs; use crate::optimizer::plan_node::generic::SourceNodeKind; use crate::optimizer::plan_node::{LogicalSource, ToStream, ToStreamContext}; use crate::session::SessionImpl; -use crate::utils::resolve_privatelink_in_with_option; +use crate::utils::{resolve_privatelink_in_with_option, resolve_secret_in_with_options}; use crate::{bind_data_type, build_graph, OptimizerContext, WithOptions}; pub(crate) const UPSTREAM_SOURCE_KEY: &str = "connector"; @@ -88,7 +89,7 @@ pub(crate) const UPSTREAM_SOURCE_KEY: &str = "connector"; /// Map a JSON schema to a relational schema async fn extract_json_table_schema( schema_config: &Option<(AstString, bool)>, - with_properties: &HashMap, + with_properties: &BTreeMap, format_encode_options: &mut BTreeMap, ) -> Result>> { match schema_config { @@ -101,14 +102,18 @@ async fn extract_json_table_schema( auth }); Ok(Some( - schema_to_columns(&schema_location.0, schema_registry_auth, with_properties) - .await? - .into_iter() - .map(|col| ColumnCatalog { - column_desc: col.into(), - is_hidden: false, - }) - .collect_vec(), + fetch_json_schema_and_map_to_columns( + &schema_location.0, + schema_registry_auth, + with_properties, + ) + .await? + .into_iter() + .map(|col| ColumnCatalog { + column_desc: col.into(), + is_hidden: false, + }) + .collect_vec(), )) } } @@ -138,7 +143,7 @@ fn json_schema_infer_use_schema_registry(schema_config: &Option<(AstString, bool /// Map an Avro schema to a relational schema. async fn extract_avro_table_schema( info: &StreamSourceInfo, - with_properties: &HashMap, + with_properties: &BTreeMap, format_encode_options: &mut BTreeMap, is_debezium: bool, ) -> Result> { @@ -174,7 +179,7 @@ async fn extract_avro_table_schema( async fn extract_debezium_avro_table_pk_columns( info: &StreamSourceInfo, - with_properties: &HashMap, + with_properties: &WithOptions, ) -> Result> { let parser_config = SpecificParserConfig::new(info, with_properties)?; let conf = DebeziumAvroParserConfig::new(parser_config.encoding_config).await?; @@ -184,7 +189,7 @@ async fn extract_debezium_avro_table_pk_columns( /// Map a protobuf schema to a relational schema. async fn extract_protobuf_table_schema( schema: &ProtobufSchema, - with_properties: &HashMap, + with_properties: &BTreeMap, format_encode_options: &mut BTreeMap, ) -> Result> { let info = StreamSourceInfo { @@ -284,12 +289,15 @@ fn get_name_strategy_or_default(name_strategy: Option) -> Result for more information. -/// return `(columns, source info)` +/// Resolves the schema of the source from external schema file. +/// See for more information. +/// +/// Note: the returned schema strictly corresponds to the schema. +/// Other special columns like additional columns (`INCLUDE`), and `row_id` column are not included. pub(crate) async fn bind_columns_from_source( session: &SessionImpl, source_schema: &ConnectorSchema, - with_properties: &HashMap, + with_properties: &BTreeMap, ) -> Result<(Option>, StreamSourceInfo)> { const MESSAGE_NAME_KEY: &str = "message"; const KEY_MESSAGE_NAME_KEY: &str = "key.message"; @@ -451,6 +459,7 @@ pub(crate) async fn bind_columns_from_source( // Parse the value but throw it away. // It would be too late to report error in `SpecificParserConfig::new`, // which leads to recovery loop. + // TODO: rely on SpecificParserConfig::new to validate, like Avro TimestamptzHandling::from_options(&format_encode_options_to_consume) .map_err(|err| InvalidInputSyntax(err.message))?; try_consume_string_from_options( @@ -508,7 +517,6 @@ pub(crate) async fn bind_columns_from_source( fn bind_columns_from_source_for_cdc( session: &SessionImpl, source_schema: &ConnectorSchema, - _with_properties: &HashMap, ) -> Result<(Option>, StreamSourceInfo)> { let format_encode_options = WithOptions::try_from(source_schema.row_options())?.into_inner(); let mut format_encode_options_to_consume = format_encode_options.clone(); @@ -554,15 +562,14 @@ fn bind_columns_from_source_for_cdc( /// add connector-spec columns to the end of column catalog pub fn handle_addition_columns( - with_properties: &HashMap, + with_properties: &BTreeMap, mut additional_columns: IncludeOption, columns: &mut Vec, + is_cdc_backfill_table: bool, ) -> Result<()> { let connector_name = with_properties.get_connector().unwrap(); // there must be a connector in source - if COMPATIBLE_ADDITIONAL_COLUMNS - .get(connector_name.as_str()) - .is_none() + if get_supported_additional_columns(connector_name.as_str(), is_cdc_backfill_table).is_none() && !additional_columns.is_empty() { return Err(RwError::from(ProtocolError(format!( @@ -593,7 +600,7 @@ pub fn handle_addition_columns( let data_type_name: Option = item .header_inner_expect_type .map(|dt| format!("{:?}", dt).to_lowercase()); - columns.push(build_additional_column_catalog( + let col = build_additional_column_desc( latest_col_id.next(), connector_name.as_str(), item.column_type.real_value().as_str(), @@ -601,7 +608,9 @@ pub fn handle_addition_columns( item.inner_field.as_deref(), data_type_name.as_deref(), true, - )?); + is_cdc_backfill_table, + )?; + columns.push(ColumnCatalog::visible(col)); } Ok(()) @@ -726,6 +735,19 @@ pub(crate) fn bind_all_columns( } } +/// TODO: perhaps put the hint in notice is better. The error message format might be not that reliable. +fn hint_upsert(encode: &Encode) -> String { + format!( + r#"Hint: For FORMAT UPSERT ENCODE {encode:}, INCLUDE KEY must be specified and the key column must be used as primary key. +example: + CREATE TABLE ( PRIMARY KEY ([rw_key | ]) ) + INCLUDE KEY [AS ] + WITH (...) + FORMAT UPSERT ENCODE {encode:} (...) +"# + ) +} + /// Bind column from source. Add key column to table columns if necessary. /// Return `pk_names`. pub(crate) async fn bind_source_pk( @@ -733,10 +755,10 @@ pub(crate) async fn bind_source_pk( source_info: &StreamSourceInfo, columns: &mut [ColumnCatalog], sql_defined_pk_names: Vec, - with_properties: &HashMap, + with_properties: &WithOptions, ) -> Result> { let sql_defined_pk = !sql_defined_pk_names.is_empty(); - let key_column_name: Option = { + let include_key_column_name: Option = { // iter columns to check if contains additional columns from key part // return the key column names if exists columns.iter().find_map(|catalog| { @@ -769,34 +791,36 @@ pub(crate) async fn bind_source_pk( // For all Upsert formats, we only accept one and only key column as primary key. // Additional KEY columns must be set in this case and must be primary key. (Format::Upsert, encode @ Encode::Json | encode @ Encode::Avro) => { - if let Some(ref key_column_name) = key_column_name + if let Some(ref key_column_name) = include_key_column_name && sql_defined_pk { - if sql_defined_pk_names.len() != 1 { - return Err(RwError::from(ProtocolError(format!( - "upsert {:?} supports only one primary key column ({}).", - encode, key_column_name - )))); - } + // pk is set. check if it's valid + // the column name have been converted to real value in `handle_addition_columns` // so we don't ignore ascii case here - if !key_column_name.eq(sql_defined_pk_names[0].as_str()) { + if sql_defined_pk_names.len() != 1 + || !key_column_name.eq(sql_defined_pk_names[0].as_str()) + { return Err(RwError::from(ProtocolError(format!( - "upsert {}'s key column {} not match with sql defined primary key {}", - encode, key_column_name, sql_defined_pk_names[0] + "Only \"{}\" can be used as primary key\n\n{}", + key_column_name, + hint_upsert(encode) )))); } sql_defined_pk_names } else { - return if key_column_name.is_none() { + // pk not set, or even key not included + return if let Some(include_key_column_name) = include_key_column_name { Err(RwError::from(ProtocolError(format!( - "INCLUDE KEY clause must be set for FORMAT UPSERT ENCODE {:?}", - encode + "Primary key must be specified to {}\n\n{}", + include_key_column_name, + hint_upsert(encode) )))) } else { Err(RwError::from(ProtocolError(format!( - "Primary key must be specified to {} when creating source with FORMAT UPSERT ENCODE {:?}", - key_column_name.unwrap(), encode)))) + "INCLUDE KEY clause not set\n\n{}", + hint_upsert(encode) + )))) }; } } @@ -844,12 +868,6 @@ pub(crate) async fn bind_source_pk( } } (Format::DebeziumMongo, Encode::Json) => { - if !additional_column_names.is_empty() { - return Err(RwError::from(ProtocolError(format!( - "FORMAT DEBEZIUMMONGO forbids additional columns, but got {:?}", - additional_column_names - )))); - } if sql_defined_pk { sql_defined_pk_names } else { @@ -899,10 +917,7 @@ pub(crate) async fn bind_source_pk( } // Add a hidden column `_rw_kafka_timestamp` to each message from Kafka source. -fn check_and_add_timestamp_column( - with_properties: &HashMap, - columns: &mut Vec, -) { +fn check_and_add_timestamp_column(with_properties: &WithOptions, columns: &mut Vec) { if with_properties.is_kafka_connector() { if columns.iter().any(|col| { matches!( @@ -915,7 +930,7 @@ fn check_and_add_timestamp_column( } // add a hidden column `_rw_kafka_timestamp` to each message from Kafka source - let mut catalog = build_additional_column_catalog( + let col = build_additional_column_desc( ColumnId::placeholder(), KAFKA_CONNECTOR, "timestamp", @@ -923,11 +938,10 @@ fn check_and_add_timestamp_column( None, None, true, + false, ) .unwrap(); - catalog.is_hidden = true; - - columns.push(catalog); + columns.push(ColumnCatalog::hidden(col)); } } @@ -1050,7 +1064,7 @@ static CONNECTORS_COMPATIBLE_FORMATS: LazyLock, + props: &mut BTreeMap, ) -> Result<()> { let connector = props .get_connector() @@ -1134,7 +1148,7 @@ pub fn validate_compatibility( /// One should only call this function after all properties of all columns are resolved, like /// generated column descriptors. pub(super) async fn check_source_schema( - props: &HashMap, + props: &WithOptions, row_id_index: Option, columns: &[ColumnCatalog], ) -> Result<()> { @@ -1143,9 +1157,9 @@ pub(super) async fn check_source_schema( }; if connector == NEXMARK_CONNECTOR { - check_nexmark_schema(props, row_id_index, columns) + check_nexmark_schema(props.inner(), row_id_index, columns) } else if connector == ICEBERG_CONNECTOR { - Ok(check_iceberg_source(props, columns) + Ok(check_iceberg_source(props.inner(), columns) .await .map_err(|err| ProtocolError(err.to_report_string()))?) } else { @@ -1154,7 +1168,7 @@ pub(super) async fn check_source_schema( } pub(super) fn check_nexmark_schema( - props: &HashMap, + props: &BTreeMap, row_id_index: Option, columns: &[ColumnCatalog], ) -> Result<()> { @@ -1208,13 +1222,13 @@ pub(super) fn check_nexmark_schema( } pub async fn extract_iceberg_columns( - with_properties: &HashMap, + with_properties: &BTreeMap, ) -> anyhow::Result> { let props = ConnectorProperties::extract(with_properties.clone(), true)?; if let ConnectorProperties::Iceberg(properties) = props { let iceberg_config: IcebergConfig = properties.to_iceberg_config(); let table = iceberg_config.load_table().await?; - let iceberg_schema: arrow_schema::Schema = table + let iceberg_schema: arrow_schema_iceberg::Schema = table .current_table_metadata() .current_schema()? .clone() @@ -1225,11 +1239,10 @@ pub async fn extract_iceberg_columns( .iter() .enumerate() .map(|(i, field)| { - let data_type = field.data_type().clone(); let column_desc = ColumnDesc::named( field.name(), - ColumnId::new((i as u32).try_into().unwrap()), - data_type.into(), + ColumnId::new((i + 1).try_into().unwrap()), + IcebergArrowConvert.type_from_field(field).unwrap(), ); ColumnCatalog { column_desc, @@ -1248,7 +1261,7 @@ pub async fn extract_iceberg_columns( } pub async fn check_iceberg_source( - props: &HashMap, + props: &BTreeMap, columns: &[ColumnCatalog], ) -> anyhow::Result<()> { let props = ConnectorProperties::extract(props.clone(), true)?; @@ -1271,7 +1284,7 @@ pub async fn check_iceberg_source( let table = iceberg_config.load_table().await?; - let iceberg_schema: arrow_schema::Schema = table + let iceberg_schema: arrow_schema_iceberg::Schema = table .current_table_metadata() .current_schema()? .clone() @@ -1292,75 +1305,111 @@ pub async fn check_iceberg_source( .filter(|f1| schema.fields.iter().any(|f2| f1.name() == &f2.name)) .cloned() .collect::>(); - let new_iceberg_schema = arrow_schema::Schema::new(new_iceberg_field); + let new_iceberg_schema = arrow_schema_iceberg::Schema::new(new_iceberg_field); - risingwave_connector::sink::iceberg::try_matches_arrow_schema( - &schema, - &new_iceberg_schema, - true, - )?; + risingwave_connector::sink::iceberg::try_matches_arrow_schema(&schema, &new_iceberg_schema)?; Ok(()) } -pub async fn handle_create_source( - handler_args: HandlerArgs, - stmt: CreateSourceStatement, -) -> Result { - let session = handler_args.session.clone(); - - if let Either::Right(resp) = session.check_relation_name_duplicated( - stmt.source_name.clone(), - StatementType::CREATE_SOURCE, - stmt.if_not_exists, - )? { - return Ok(resp); +pub fn bind_connector_props( + handler_args: &HandlerArgs, + source_schema: &ConnectorSchema, + is_create_source: bool, +) -> Result { + let mut with_properties = handler_args.with_options.clone().into_connector_props(); + validate_compatibility(source_schema, &mut with_properties)?; + let create_cdc_source_job = with_properties.is_shareable_cdc_connector(); + if is_create_source && create_cdc_source_job { + // set connector to backfill mode + with_properties.insert(CDC_SNAPSHOT_MODE_KEY.into(), CDC_SNAPSHOT_BACKFILL.into()); + // enable cdc sharing mode, which will capture all tables in the given `database.name` + with_properties.insert(CDC_SHARING_MODE_KEY.into(), "true".into()); + // enable transactional cdc + with_properties.insert(CDC_TRANSACTIONAL_KEY.into(), "true".into()); + with_properties.insert( + CDC_WAIT_FOR_STREAMING_START_TIMEOUT.into(), + handler_args + .session + .config() + .cdc_source_wait_streaming_start_timeout() + .to_string(), + ); } + Ok(WithOptions::new(with_properties)) +} - let db_name = session.database(); - let (schema_name, name) = Binder::resolve_schema_qualified_name(db_name, stmt.source_name)?; +#[allow(clippy::too_many_arguments)] +pub async fn bind_create_source_or_table_with_connector( + handler_args: HandlerArgs, + full_name: ObjectName, + source_schema: ConnectorSchema, + with_properties: WithOptions, + sql_columns_defs: &[ColumnDef], + constraints: Vec, + wildcard_idx: Option, + source_watermarks: Vec, + columns_from_resolve_source: Option>, + source_info: StreamSourceInfo, + include_column_options: IncludeOption, + col_id_gen: &mut ColumnIdGenerator, + // `true` for "create source", `false` for "create table with connector" + is_create_source: bool, +) -> Result<(SourceCatalog, DatabaseId, SchemaId)> { + let session = &handler_args.session; + let db_name: &str = session.database(); + let (schema_name, source_name) = Binder::resolve_schema_qualified_name(db_name, full_name)?; let (database_id, schema_id) = session.get_database_and_schema_id_for_create(schema_name.clone())?; - if handler_args.with_options.is_empty() { - return Err(RwError::from(InvalidInputSyntax( - "missing WITH clause".to_string(), - ))); + if !is_create_source && with_properties.is_iceberg_connector() { + return Err(ErrorCode::BindError( + "can't CREATE TABLE with iceberg connector\n\nHint: use CREATE SOURCE instead" + .to_string(), + ) + .into()); + } + if is_create_source { + match source_schema.format { + Format::Upsert => { + return Err(ErrorCode::BindError(format!( + "can't CREATE SOURCE with FORMAT UPSERT\n\nHint: use CREATE TABLE instead\n\n{}", + hint_upsert(&source_schema.row_encode) + )) + .into()); + } + _ => { + // TODO: enhance error message for other formats + } + } } - let source_schema = stmt.source_schema.into_v2_with_warning(); - - let mut with_properties = handler_args.with_options.clone().into_connector_props(); - validate_compatibility(&source_schema, &mut with_properties)?; - - ensure_table_constraints_supported(&stmt.constraints)?; - let sql_pk_names = bind_sql_pk_names(&stmt.columns, &stmt.constraints)?; + ensure_table_constraints_supported(&constraints)?; + let sql_pk_names = bind_sql_pk_names(sql_columns_defs, &constraints)?; - let create_cdc_source_job = with_properties.is_shareable_cdc_connector(); - let is_shared = create_cdc_source_job - || (with_properties.is_kafka_connector() && session.config().rw_enable_shared_source()); - - let (columns_from_resolve_source, mut source_info) = if create_cdc_source_job { - bind_columns_from_source_for_cdc(&session, &source_schema, &with_properties)? - } else { - bind_columns_from_source(&session, &source_schema, &with_properties).await? - }; - if is_shared { - // Note: this field should be called is_shared. Check field doc for more details. - source_info.cdc_source_job = true; - source_info.is_distributed = !create_cdc_source_job; - } - let columns_from_sql = bind_sql_columns(&stmt.columns)?; + let columns_from_sql = bind_sql_columns(sql_columns_defs)?; let mut columns = bind_all_columns( &source_schema, columns_from_resolve_source, columns_from_sql, - &stmt.columns, - stmt.wildcard_idx, + sql_columns_defs, + wildcard_idx, )?; + // add additional columns before bind pk, because `format upsert` requires the key column - handle_addition_columns(&with_properties, stmt.include_column_options, &mut columns)?; + handle_addition_columns( + &with_properties, + include_column_options, + &mut columns, + false, + )?; + // compatible with the behavior that add a hidden column `_rw_kafka_timestamp` to each message from Kafka source + if is_create_source { + // must behind `handle_addition_columns` + check_and_add_timestamp_column(&with_properties, &mut columns); + } + let pk_names = bind_source_pk( &source_schema, &source_info, @@ -1370,79 +1419,142 @@ pub async fn handle_create_source( ) .await?; - if create_cdc_source_job { - // set connector to backfill mode - with_properties.insert(CDC_SNAPSHOT_MODE_KEY.into(), CDC_SNAPSHOT_BACKFILL.into()); - // enable cdc sharing mode, which will capture all tables in the given `database.name` - with_properties.insert(CDC_SHARING_MODE_KEY.into(), "true".into()); - // enable transactional cdc - with_properties.insert(CDC_TRANSACTIONAL_KEY.into(), "true".into()); - } - - // must behind `handle_addition_columns` - check_and_add_timestamp_column(&with_properties, &mut columns); - - let mut col_id_gen = ColumnIdGenerator::new_initial(); - for c in &mut columns { - c.column_desc.column_id = col_id_gen.generate(c.name()) - } - - if !pk_names.is_empty() { + if is_create_source && !pk_names.is_empty() { return Err(ErrorCode::InvalidInputSyntax( "Source does not support PRIMARY KEY constraint, please use \"CREATE TABLE\" instead" .to_owned(), ) .into()); } - let (mut columns, pk_column_ids, row_id_index) = - bind_pk_on_relation(columns, pk_names, with_properties.connector_need_pk())?; - debug_assert!(is_column_ids_dedup(&columns)); + // XXX: why do we use col_id_gen here? It doesn't seem to be very necessary. + // XXX: should we also chenge the col id for struct fields? + for c in &mut columns { + c.column_desc.column_id = col_id_gen.generate(c.name()) + } + debug_assert_column_ids_distinct(&columns); + + let must_need_pk = if is_create_source { + with_properties.connector_need_pk() + } else { + // For those connectors that do not need generate a `row_id`` column in the source schema such as iceberg. + // But in such case, we can not create mv or table on the source because there is not a pk. + assert!(with_properties.connector_need_pk()); + + true + }; + + let (mut columns, pk_col_ids, row_id_index) = + bind_pk_and_row_id_on_relation(columns, pk_names, must_need_pk)?; let watermark_descs = - bind_source_watermark(&session, name.clone(), stmt.source_watermarks, &columns)?; + bind_source_watermark(session, source_name.clone(), source_watermarks, &columns)?; // TODO(yuhao): allow multiple watermark on source. assert!(watermark_descs.len() <= 1); bind_sql_column_constraints( - &session, - name.clone(), + session, + source_name.clone(), &mut columns, - stmt.columns, - &pk_column_ids, + // TODO(st1page): pass the ref + sql_columns_defs.to_vec(), + &pk_col_ids, )?; - check_source_schema(&with_properties, row_id_index, &columns).await?; - let pk_column_ids = pk_column_ids.into_iter().map(Into::into).collect(); - - let mut with_options = WithOptions::new(with_properties); - // resolve privatelink connection for Kafka source + // resolve privatelink connection for Kafka + let mut with_properties = with_properties; let connection_id = - resolve_privatelink_in_with_option(&mut with_options, &schema_name, &session)?; - let definition = handler_args.normalized_sql.clone(); + resolve_privatelink_in_with_option(&mut with_properties, &schema_name, session)?; + let _secret_ref = resolve_secret_in_with_options(&mut with_properties, session)?; + + let definition: String = handler_args.normalized_sql.clone(); - let source = PbSource { + let associated_table_id = if is_create_source { + None + } else { + Some(TableId::placeholder()) + }; + let source = SourceCatalog { id: TableId::placeholder().table_id, - schema_id, - database_id, - name, - row_id_index: row_id_index.map(|idx| idx as u32), - columns: columns.iter().map(|c| c.to_protobuf()).collect_vec(), - pk_column_ids, - with_properties: with_options.into_inner().into_iter().collect(), - info: Some(source_info), + name: source_name, + columns, + pk_col_ids, + append_only: row_id_index.is_some(), owner: session.user_id(), + info: source_info, + row_id_index, + with_properties: with_properties.into_inner().into_iter().collect(), watermark_descs, + associated_table_id, definition, connection_id, - initialized_at_epoch: None, created_at_epoch: None, - optional_associated_table_id: None, + initialized_at_epoch: None, version: INITIAL_SOURCE_VERSION_ID, - initialized_at_cluster_version: None, created_at_cluster_version: None, + initialized_at_cluster_version: None, }; + Ok((source, database_id, schema_id)) +} + +pub async fn handle_create_source( + handler_args: HandlerArgs, + stmt: CreateSourceStatement, +) -> Result { + let session = handler_args.session.clone(); + + if let Either::Right(resp) = session.check_relation_name_duplicated( + stmt.source_name.clone(), + StatementType::CREATE_SOURCE, + stmt.if_not_exists, + )? { + return Ok(resp); + } + + if handler_args.with_options.is_empty() { + return Err(RwError::from(InvalidInputSyntax( + "missing WITH clause".to_string(), + ))); + } + + let source_schema = stmt.source_schema.into_v2_with_warning(); + let with_properties = bind_connector_props(&handler_args, &source_schema, true)?; + + let create_cdc_source_job = with_properties.is_shareable_cdc_connector(); + let is_shared = create_cdc_source_job + || (with_properties.is_kafka_connector() && session.config().rw_enable_shared_source()); + + let (columns_from_resolve_source, mut source_info) = if create_cdc_source_job { + bind_columns_from_source_for_cdc(&session, &source_schema)? + } else { + bind_columns_from_source(&session, &source_schema, &with_properties).await? + }; + if is_shared { + // Note: this field should be called is_shared. Check field doc for more details. + source_info.cdc_source_job = true; + source_info.is_distributed = !create_cdc_source_job; + } + let mut col_id_gen = ColumnIdGenerator::new_initial(); + + let (source_catalog, database_id, schema_id) = bind_create_source_or_table_with_connector( + handler_args.clone(), + stmt.source_name, + source_schema, + with_properties, + &stmt.columns, + stmt.constraints, + stmt.wildcard_idx, + stmt.source_watermarks, + columns_from_resolve_source, + source_info, + stmt.include_column_options, + &mut col_id_gen, + true, + ) + .await?; + + let source = source_catalog.to_prost(schema_id, database_id); let catalog_writer = session.catalog_writer()?; @@ -1450,7 +1562,7 @@ pub async fn handle_create_source( let graph = { let context = OptimizerContext::from_handler_args(handler_args); let source_node = LogicalSource::with_catalog( - Rc::new(SourceCatalog::from(&source)), + Rc::new(source_catalog), SourceNodeKind::CreateSharedSource, context.into(), None, @@ -1501,6 +1613,7 @@ fn row_encode_to_prost(row_encode: &Encode) -> EncodeType { Encode::Template => EncodeType::Template, Encode::Parquet => EncodeType::Parquet, Encode::None => EncodeType::None, + Encode::Text => EncodeType::Text, } } @@ -1638,7 +1751,7 @@ pub mod tests { ); // Options are not merged into props. - assert!(source.with_properties.get("schema.location").is_none()); + assert!(!source.with_properties.contains_key("schema.location")); } #[tokio::test] @@ -1713,28 +1826,6 @@ pub mod tests { }; assert_eq!(columns, expect_columns); - // test derive include column for format upsert - let sql = "CREATE SOURCE s1 (v1 int) with (connector = 'kafka') format upsert encode json" - .to_string(); - match frontend.run_sql(sql).await { - Err(e) => { - assert_eq!( - e.to_string(), - "Protocol error: INCLUDE KEY clause must be set for FORMAT UPSERT ENCODE Json" - ) - } - _ => unreachable!(), - } - - let sql = "CREATE SOURCE s2 (v1 int) include key as _rw_kafka_key with (connector = 'kafka') format upsert encode json" - .to_string(); - match frontend.run_sql(sql).await { - Err(e) => { - assert_eq!(e.to_string(), "Protocol error: Primary key must be specified to _rw_kafka_key when creating source with FORMAT UPSERT ENCODE Json") - } - _ => unreachable!(), - } - let sql = "CREATE SOURCE s3 (v1 int) include timestamp 'header1' as header_col with (connector = 'kafka') format plain encode json" .to_string(); diff --git a/src/frontend/src/handler/create_sql_function.rs b/src/frontend/src/handler/create_sql_function.rs index ae7b2730e25d5..9b5d34c34abe8 100644 --- a/src/frontend/src/handler/create_sql_function.rs +++ b/src/frontend/src/handler/create_sql_function.rs @@ -15,15 +15,10 @@ use std::collections::HashMap; use fancy_regex::Regex; -use itertools::Itertools; -use pgwire::pg_response::StatementType; use risingwave_common::catalog::FunctionId; use risingwave_common::types::DataType; use risingwave_pb::catalog::function::{Kind, ScalarFunction, TableFunction}; use risingwave_pb::catalog::Function; -use risingwave_sqlparser::ast::{ - CreateFunctionBody, FunctionDefinition, ObjectName, OperateFunctionArg, -}; use risingwave_sqlparser::parser::{Parser, ParserError}; use super::*; @@ -155,6 +150,9 @@ pub async fn handle_create_sql_function( let body = match ¶ms.as_ { Some(FunctionDefinition::SingleQuotedDef(s)) => s.clone(), Some(FunctionDefinition::DoubleDollarDef(s)) => s.clone(), + Some(FunctionDefinition::Identifier(_)) => { + return Err(ErrorCode::InvalidParameterValue("expect quoted string".to_string()).into()) + } None => { if params.return_.is_none() { return Err(ErrorCode::InvalidParameterValue( diff --git a/src/frontend/src/handler/create_subscription.rs b/src/frontend/src/handler/create_subscription.rs index 371806cc93a48..8d4ed82cc82ee 100644 --- a/src/frontend/src/handler/create_subscription.rs +++ b/src/frontend/src/handler/create_subscription.rs @@ -17,94 +17,56 @@ use std::rc::Rc; use either::Either; use pgwire::pg_response::{PgResponse, StatementType}; use risingwave_common::catalog::UserId; -use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; -use risingwave_sqlparser::ast::{CreateSubscriptionStatement, Query}; +use risingwave_sqlparser::ast::CreateSubscriptionStatement; -use super::privilege::resolve_query_privileges; -use super::util::gen_query_from_table_name; use super::{HandlerArgs, RwPgResponse}; -use crate::catalog::subscription_catalog::SubscriptionCatalog; +use crate::catalog::subscription_catalog::{SubscriptionCatalog, SubscriptionId}; use crate::error::Result; -use crate::optimizer::RelationCollectorVisitor; use crate::scheduler::streaming_manager::CreatingStreamingJobInfo; use crate::session::SessionImpl; -use crate::{ - build_graph, Binder, Explain, OptimizerContext, OptimizerContextRef, PlanRef, Planner, -}; +use crate::{Binder, OptimizerContext, OptimizerContextRef}; -// used to store result of `gen_subscription_plan` -pub struct SubscriptionPlanContext { - pub query: Box, - pub subscription_plan: PlanRef, - pub subscription_catalog: SubscriptionCatalog, -} - -pub fn gen_subscription_plan( +pub fn create_subscription_catalog( session: &SessionImpl, context: OptimizerContextRef, stmt: CreateSubscriptionStatement, -) -> Result { +) -> Result { let db_name = session.database(); - let (schema_name, subscription_name) = + let (subscription_schema_name, subscription_name) = Binder::resolve_schema_qualified_name(db_name, stmt.subscription_name.clone())?; - let subscription_from_table_name = stmt - .subscription_from - .0 - .last() - .unwrap() - .real_value() - .clone(); - let query = Box::new(gen_query_from_table_name(stmt.subscription_from)); - - let (database_id, schema_id) = - session.get_database_and_schema_id_for_create(schema_name.clone())?; - + let (table_schema_name, subscription_from_table_name) = + Binder::resolve_schema_qualified_name(db_name, stmt.subscription_from.clone())?; + let (table_database_id, table_schema_id) = + session.get_database_and_schema_id_for_create(table_schema_name.clone())?; + let (subscription_database_id, subscription_schema_id) = + session.get_database_and_schema_id_for_create(subscription_schema_name.clone())?; let definition = context.normalized_sql().to_owned(); - - let (dependent_relations, bound) = { - let mut binder = Binder::new_for_stream(session); - let bound = binder.bind_query(*query.clone())?; - (binder.included_relations(), bound) - }; - - let check_items = resolve_query_privileges(&bound); - session.check_privileges(&check_items)?; - - let with_options = context.with_options().clone(); - let mut plan_root = Planner::new(context).plan_query(bound)?; - - let subscription_plan = plan_root.gen_subscription_plan( - database_id, - schema_id, - dependent_relations.clone(), - subscription_name, + let dependent_table_id = session + .get_table_by_name( + &subscription_from_table_name, + table_database_id, + table_schema_id, + )? + .id; + + let mut subscription_catalog = SubscriptionCatalog { + id: SubscriptionId::placeholder(), + name: subscription_name, definition, - with_options, - false, - subscription_from_table_name, - UserId::new(session.user_id()), - )?; - - let subscription_catalog = subscription_plan.subscription_catalog(); - - let subscription_plan: PlanRef = subscription_plan.into(); - - let ctx = subscription_plan.ctx(); - let explain_trace = ctx.is_explain_trace(); - if explain_trace { - ctx.trace("Create Subscription:"); - ctx.trace(subscription_plan.explain_to_string()); - } + retention_seconds: 0, + database_id: subscription_database_id, + schema_id: subscription_schema_id, + dependent_table_id, + owner: UserId::new(session.user_id()), + initialized_at_epoch: None, + created_at_epoch: None, + created_at_cluster_version: None, + initialized_at_cluster_version: None, + }; - let dependent_relations = - RelationCollectorVisitor::collect_with(dependent_relations, subscription_plan.clone()); + subscription_catalog.set_retention_seconds(context.with_options())?; - let subscription_catalog = subscription_catalog.add_dependent_relations(dependent_relations); - Ok(SubscriptionPlanContext { - query, - subscription_plan, - subscription_catalog, - }) + Ok(subscription_catalog) } pub async fn handle_create_subscription( @@ -120,27 +82,9 @@ pub async fn handle_create_subscription( )? { return Ok(resp); }; - - let (subscription_catalog, graph) = { + let subscription_catalog = { let context = Rc::new(OptimizerContext::from_handler_args(handle_args)); - - let SubscriptionPlanContext { - query: _, - subscription_plan, - subscription_catalog, - } = gen_subscription_plan(&session, context.clone(), stmt)?; - - let mut graph = build_graph(subscription_plan)?; - - graph.parallelism = - session - .config() - .streaming_parallelism() - .map(|parallelism| Parallelism { - parallelism: parallelism.get(), - }); - - (subscription_catalog, graph) + create_subscription_catalog(&session, context.clone(), stmt)? }; let _job_guard = @@ -156,7 +100,7 @@ pub async fn handle_create_subscription( let catalog_writer = session.catalog_writer()?; catalog_writer - .create_subscription(subscription_catalog.to_proto(), graph) + .create_subscription(subscription_catalog.to_proto()) .await?; Ok(PgResponse::empty_result(StatementType::CREATE_SUBSCRIPTION)) diff --git a/src/frontend/src/handler/create_table.rs b/src/frontend/src/handler/create_table.rs index 9c1290b59ba50..11d0d0ebd08dd 100644 --- a/src/frontend/src/handler/create_table.rs +++ b/src/frontend/src/handler/create_table.rs @@ -14,10 +14,9 @@ use std::collections::{BTreeMap, HashMap}; use std::rc::Rc; -use std::str::FromStr; use std::sync::Arc; -use anyhow::anyhow; +use anyhow::{anyhow, Context}; use either::Either; use fixedbitset::FixedBitSet; use itertools::Itertools; @@ -25,17 +24,15 @@ use pgwire::pg_response::{PgResponse, StatementType}; use risingwave_common::bail_not_implemented; use risingwave_common::catalog::{ CdcTableDesc, ColumnCatalog, ColumnDesc, TableId, TableVersionId, DEFAULT_SCHEMA_NAME, - INITIAL_SOURCE_VERSION_ID, INITIAL_TABLE_VERSION_ID, USER_COLUMN_ID_OFFSET, + INITIAL_TABLE_VERSION_ID, }; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_common::util::value_encoding::DatumToProtoExt; +use risingwave_connector::source; use risingwave_connector::source::cdc::external::{ - DATABASE_NAME_KEY, SCHEMA_NAME_KEY, TABLE_NAME_KEY, + ExternalTableConfig, ExternalTableImpl, DATABASE_NAME_KEY, SCHEMA_NAME_KEY, TABLE_NAME_KEY, }; -use risingwave_connector::source::cdc::CDC_BACKFILL_ENABLE_KEY; -use risingwave_connector::{source, WithPropertiesExt}; -use risingwave_pb::catalog::source::OptionalAssociatedTableId; -use risingwave_pb::catalog::{PbSource, PbTable, StreamSourceInfo, Table, WatermarkDesc}; +use risingwave_pb::catalog::{PbSource, PbTable, Table, WatermarkDesc}; use risingwave_pb::ddl_service::TableJobType; use risingwave_pb::plan_common::column_desc::GeneratedOrDefaultColumn; use risingwave_pb::plan_common::{ @@ -54,21 +51,20 @@ use crate::binder::{bind_data_type, bind_struct_field, Clause}; use crate::catalog::root_catalog::SchemaPath; use crate::catalog::source_catalog::SourceCatalog; use crate::catalog::table_catalog::TableVersion; -use crate::catalog::{check_valid_column_name, ColumnId}; +use crate::catalog::{check_valid_column_name, ColumnId, DatabaseId, SchemaId}; use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{Expr, ExprImpl, ExprRewriter, InlineNowProcTime}; use crate::handler::create_source::{ - bind_all_columns, bind_columns_from_source, bind_source_pk, bind_source_watermark, - check_source_schema, handle_addition_columns, validate_compatibility, UPSTREAM_SOURCE_KEY, + bind_columns_from_source, bind_connector_props, bind_create_source_or_table_with_connector, + bind_source_watermark, handle_addition_columns, UPSTREAM_SOURCE_KEY, }; use crate::handler::HandlerArgs; -use crate::optimizer::plan_node::generic::SourceNodeKind; +use crate::optimizer::plan_node::generic::{CdcScanOptions, SourceNodeKind}; use crate::optimizer::plan_node::{LogicalCdcScan, LogicalSource}; use crate::optimizer::property::{Order, RequiredDist}; use crate::optimizer::{OptimizerContext, OptimizerContextRef, PlanRef, PlanRoot}; use crate::session::SessionImpl; use crate::stream_fragmenter::build_graph; -use crate::utils::resolve_privatelink_in_with_option; use crate::{Binder, TableCatalog, WithOptions}; /// Column ID generator for a new table or a new version of an existing table to alter. @@ -114,7 +110,7 @@ impl ColumnIdGenerator { pub fn new_initial() -> Self { Self { existing: HashMap::new(), - next_column_id: ColumnId::from(USER_COLUMN_ID_OFFSET), + next_column_id: ColumnId::first_user_column(), version_id: INITIAL_TABLE_VERSION_ID, } } @@ -408,7 +404,7 @@ fn multiple_pk_definition_err() -> RwError { /// /// It returns the columns together with `pk_column_ids`, and an optional row id column index if /// added. -pub fn bind_pk_on_relation( +pub fn bind_pk_and_row_id_on_relation( mut columns: Vec, pk_names: Vec, must_need_pk: bool, @@ -482,85 +478,44 @@ pub(crate) async fn gen_create_table_plan_with_source( } let session = &handler_args.session; - let mut with_properties = handler_args.with_options.clone().into_connector_props(); - validate_compatibility(&source_schema, &mut with_properties)?; - - ensure_table_constraints_supported(&constraints)?; - - let sql_pk_names = bind_sql_pk_names(&column_defs, &constraints)?; + let with_properties = bind_connector_props(&handler_args, &source_schema, false)?; let (columns_from_resolve_source, source_info) = bind_columns_from_source(session, &source_schema, &with_properties).await?; - let columns_from_sql = bind_sql_columns(&column_defs)?; - let mut columns = bind_all_columns( - &source_schema, - columns_from_resolve_source, - columns_from_sql, + let (source_catalog, database_id, schema_id) = bind_create_source_or_table_with_connector( + handler_args.clone(), + table_name, + source_schema, + with_properties, &column_defs, + constraints, wildcard_idx, - )?; - - // add additional columns before bind pk, because `format upsert` requires the key column - handle_addition_columns(&with_properties, include_column_options, &mut columns)?; - let pk_names = bind_source_pk( - &source_schema, - &source_info, - &mut columns, - sql_pk_names, - &with_properties, + source_watermarks, + columns_from_resolve_source, + source_info, + include_column_options, + &mut col_id_gen, + false, ) .await?; - for c in &mut columns { - c.column_desc.column_id = col_id_gen.generate(c.name()) - } - - if with_properties.is_iceberg_connector() { - return Err( - ErrorCode::BindError("can't create table with iceberg connector".to_string()).into(), - ); - } - let (mut columns, pk_column_ids, row_id_index) = bind_pk_on_relation(columns, pk_names, true)?; - - let watermark_descs = bind_source_watermark( - session, - table_name.real_value(), - source_watermarks, - &columns, - )?; - // TODO(yuhao): allow multiple watermark on source. - assert!(watermark_descs.len() <= 1); - - let definition = handler_args.normalized_sql.clone(); - - bind_sql_column_constraints( - session, - table_name.real_value(), - &mut columns, - column_defs, - &pk_column_ids, - )?; - - check_source_schema(&with_properties, row_id_index, &columns).await?; + let pb_source = source_catalog.to_prost(schema_id, database_id); let context = OptimizerContext::new(handler_args, explain_options); - gen_table_plan_inner( + let (plan, table) = gen_table_plan_with_source( context.into(), - table_name, - columns, - with_properties, - pk_column_ids, - row_id_index, - Some(source_info), - definition, - watermark_descs, + source_catalog, append_only, on_conflict, with_version_column, Some(col_id_gen.into_version()), - ) + database_id, + schema_id, + )?; + + Ok((plan, Some(pb_source), table)) } /// `gen_create_table_plan` generates the plan for creating a table without an external stream @@ -575,20 +530,18 @@ pub(crate) fn gen_create_table_plan( append_only: bool, on_conflict: Option, with_version_column: Option, -) -> Result<(PlanRef, Option, PbTable)> { +) -> Result<(PlanRef, PbTable)> { let definition = context.normalized_sql().to_owned(); let mut columns = bind_sql_columns(&column_defs)?; for c in &mut columns { c.column_desc.column_id = col_id_gen.generate(c.name()) } - let with_properties = context.with_options().inner().clone().into_iter().collect(); - gen_create_table_plan_without_bind( + gen_create_table_plan_without_source( context, table_name, columns, column_defs, constraints, - with_properties, definition, source_watermarks, append_only, @@ -599,25 +552,25 @@ pub(crate) fn gen_create_table_plan( } #[allow(clippy::too_many_arguments)] -pub(crate) fn gen_create_table_plan_without_bind( +pub(crate) fn gen_create_table_plan_without_source( context: OptimizerContext, table_name: ObjectName, columns: Vec, column_defs: Vec, constraints: Vec, - with_properties: HashMap, definition: String, source_watermarks: Vec, append_only: bool, on_conflict: Option, with_version_column: Option, version: Option, -) -> Result<(PlanRef, Option, PbTable)> { +) -> Result<(PlanRef, PbTable)> { ensure_table_constraints_supported(&constraints)?; let pk_names = bind_sql_pk_names(&column_defs, &constraints)?; - let (mut columns, pk_column_ids, row_id_index) = bind_pk_on_relation(columns, pk_names, true)?; + let (mut columns, pk_column_ids, row_id_index) = + bind_pk_and_row_id_on_relation(columns, pk_names, true)?; - let watermark_descs = bind_source_watermark( + let watermark_descs: Vec = bind_source_watermark( context.session_ctx(), table_name.real_value(), source_watermarks, @@ -631,33 +584,68 @@ pub(crate) fn gen_create_table_plan_without_bind( column_defs, &pk_column_ids, )?; + let session = context.session_ctx().clone(); + + let db_name = session.database(); + let (schema_name, name) = Binder::resolve_schema_qualified_name(db_name, table_name)?; + let (database_id, schema_id) = + session.get_database_and_schema_id_for_create(schema_name.clone())?; gen_table_plan_inner( context.into(), - table_name, + name, columns, - with_properties, pk_column_ids, row_id_index, - None, definition, watermark_descs, append_only, on_conflict, with_version_column, version, + None, + database_id, + schema_id, + ) +} + +fn gen_table_plan_with_source( + context: OptimizerContextRef, + source_catalog: SourceCatalog, + append_only: bool, + on_conflict: Option, + with_version_column: Option, + version: Option, /* TODO: this should always be `Some` if we support `ALTER + * TABLE` for `CREATE TABLE AS`. */ + database_id: DatabaseId, + schema_id: SchemaId, +) -> Result<(PlanRef, PbTable)> { + let cloned_source_catalog = source_catalog.clone(); + gen_table_plan_inner( + context, + source_catalog.name, + source_catalog.columns, + source_catalog.pk_col_ids, + source_catalog.row_id_index, + source_catalog.definition, + source_catalog.watermark_descs, + append_only, + on_conflict, + with_version_column, + version, + Some(cloned_source_catalog), + database_id, + schema_id, ) } #[allow(clippy::too_many_arguments)] fn gen_table_plan_inner( context: OptimizerContextRef, - table_name: ObjectName, + table_name: String, columns: Vec, - with_properties: HashMap, pk_column_ids: Vec, row_id_index: Option, - source_info: Option, definition: String, watermark_descs: Vec, append_only: bool, @@ -665,51 +653,15 @@ fn gen_table_plan_inner( with_version_column: Option, version: Option, /* TODO: this should always be `Some` if we support `ALTER * TABLE` for `CREATE TABLE AS`. */ -) -> Result<(PlanRef, Option, PbTable)> { + source_catalog: Option, + database_id: DatabaseId, + schema_id: SchemaId, +) -> Result<(PlanRef, PbTable)> { let session = context.session_ctx().clone(); - let db_name = session.database(); - let (schema_name, name) = Binder::resolve_schema_qualified_name(db_name, table_name)?; - let (database_id, schema_id) = - session.get_database_and_schema_id_for_create(schema_name.clone())?; - - // resolve privatelink connection for Table backed by Kafka source - let mut with_properties = WithOptions::new(with_properties); - let connection_id = - resolve_privatelink_in_with_option(&mut with_properties, &schema_name, &session)?; - let retention_seconds = with_properties.retention_seconds(); - - let is_external_source = source_info.is_some(); - - let source = source_info.map(|source_info| PbSource { - id: TableId::placeholder().table_id, - schema_id, - database_id, - name: name.clone(), - row_id_index: row_id_index.map(|i| i as _), - columns: columns - .iter() - .map(|column| column.to_protobuf()) - .collect_vec(), - pk_column_ids: pk_column_ids.iter().map(Into::into).collect_vec(), - with_properties: with_properties.into_inner().into_iter().collect(), - info: Some(source_info), - owner: session.user_id(), - watermark_descs: watermark_descs.clone(), - definition: "".to_string(), - connection_id, - initialized_at_epoch: None, - created_at_epoch: None, - optional_associated_table_id: Some(OptionalAssociatedTableId::AssociatedTableId( - TableId::placeholder().table_id, - )), - version: INITIAL_SOURCE_VERSION_ID, - initialized_at_cluster_version: None, - created_at_cluster_version: None, - }); - - let source_catalog = source.as_ref().map(|source| Rc::new((source).into())); + let retention_seconds = context.with_options().retention_seconds(); + let is_external_source = source_catalog.is_some(); let source_node: PlanRef = LogicalSource::new( - source_catalog.clone(), + source_catalog.map(|source| Rc::new(source.clone())), columns.clone(), row_id_index, SourceNodeKind::CreateTable, @@ -719,7 +671,7 @@ fn gen_table_plan_inner( .into(); let required_cols = FixedBitSet::with_capacity(columns.len()); - let mut plan_root = PlanRoot::new( + let plan_root = PlanRoot::new_with_logical_plan( source_node, RequiredDist::Any, Order::any(), @@ -752,7 +704,7 @@ fn gen_table_plan_inner( let materialize = plan_root.gen_table_plan( context, - name, + table_name, columns, definition, pk_column_ids, @@ -769,52 +721,47 @@ fn gen_table_plan_inner( let mut table = materialize.table().to_prost(schema_id, database_id); table.owner = session.user_id(); - Ok((materialize.into(), source, table)) + Ok((materialize.into(), table)) } +/// Generate stream plan for cdc table based on shared source. +/// In replace workflow, the `table_id` is the id of the table to be replaced +/// in create table workflow, the `table_id` is a placeholder will be filled in the Meta #[allow(clippy::too_many_arguments)] -pub(crate) fn gen_create_table_plan_for_cdc_source( - context: OptimizerContextRef, - source_name: ObjectName, - table_name: ObjectName, +pub(crate) fn gen_create_table_plan_for_cdc_table( + handler_args: HandlerArgs, + explain_options: ExplainOptions, + source: Arc, external_table_name: String, - column_defs: Vec, - constraints: Vec, + mut columns: Vec, + pk_names: Vec, + connect_properties: BTreeMap, mut col_id_gen: ColumnIdGenerator, on_conflict: Option, with_version_column: Option, + include_column_options: IncludeOption, + resolved_table_name: String, + database_id: DatabaseId, + schema_id: SchemaId, + table_id: TableId, ) -> Result<(PlanRef, PbTable)> { + let context: OptimizerContextRef = OptimizerContext::new(handler_args, explain_options).into(); let session = context.session_ctx().clone(); - let db_name = session.database(); - let (schema_name, name) = Binder::resolve_schema_qualified_name(db_name, table_name)?; - let (database_id, schema_id) = - session.get_database_and_schema_id_for_create(schema_name.clone())?; - // cdc table cannot be append-only - let append_only = false; - let (source_schema, source_name) = Binder::resolve_schema_qualified_name(db_name, source_name)?; - - let source = { - let catalog_reader = session.env().catalog_reader().read_guard(); - let schema_name = source_schema - .clone() - .unwrap_or(DEFAULT_SCHEMA_NAME.to_string()); - let (source, _) = catalog_reader.get_source_by_name( - db_name, - SchemaPath::Name(schema_name.as_str()), - source_name.as_str(), - )?; - source.clone() - }; - - let mut columns = bind_sql_columns(&column_defs)?; + // append additional columns to the end + handle_addition_columns( + &connect_properties, + include_column_options, + &mut columns, + true, + )?; for c in &mut columns { c.column_desc.column_id = col_id_gen.generate(c.name()) } - let pk_names = bind_sql_pk_names(&column_defs, &constraints)?; - let (columns, pk_column_ids, _) = bind_pk_on_relation(columns, pk_names, true)?; + let (columns, pk_column_ids, _row_id_index) = + bind_pk_and_row_id_on_relation(columns, pk_names, true)?; let definition = context.normalized_sql().to_owned(); @@ -834,41 +781,30 @@ pub(crate) fn gen_create_table_plan_for_cdc_source( .map(|idx| ColumnOrder::new(*idx, OrderType::ascending())) .collect(); - let connect_properties = - derive_connect_properties(source.as_ref(), external_table_name.clone())?; - let cdc_table_desc = CdcTableDesc { - table_id: TableId::placeholder(), // will be filled in meta node - source_id: source.id.into(), // id of cdc source streaming job + table_id, + source_id: source.id.into(), // id of cdc source streaming job external_table_name: external_table_name.clone(), pk: table_pk, columns: columns.iter().map(|c| c.column_desc.clone()).collect(), stream_key: pk_column_indices, - value_indices: (0..columns.len()).collect_vec(), - connect_properties, + connect_properties: connect_properties.into_iter().collect(), }; tracing::debug!(?cdc_table_desc, "create cdc table"); - // disable backfill if 'snapshot=false' - let disable_backfill = match context.with_options().get(CDC_BACKFILL_ENABLE_KEY) { - None => false, - Some(v) => { - !(bool::from_str(v) - .map_err(|_| anyhow!("Invalid value for {}", CDC_BACKFILL_ENABLE_KEY))?) - } - }; + let options = CdcScanOptions::from_with_options(context.with_options())?; let logical_scan = LogicalCdcScan::create( external_table_name, Rc::new(cdc_table_desc), context.clone(), - disable_backfill, + options, ); let scan_node: PlanRef = logical_scan.into(); let required_cols = FixedBitSet::with_capacity(columns.len()); - let mut plan_root = PlanRoot::new( + let plan_root = PlanRoot::new_with_logical_plan( scan_node, RequiredDist::Any, Order::any(), @@ -878,12 +814,12 @@ pub(crate) fn gen_create_table_plan_for_cdc_source( let materialize = plan_root.gen_table_plan( context, - name, + resolved_table_name, columns, definition, pk_column_ids, None, - append_only, + false, on_conflict, with_version_column, vec![], @@ -900,13 +836,13 @@ pub(crate) fn gen_create_table_plan_for_cdc_source( } fn derive_connect_properties( - source: &SourceCatalog, + source_with_properties: &BTreeMap, external_table_name: String, ) -> Result> { use source::cdc::{MYSQL_CDC_CONNECTOR, POSTGRES_CDC_CONNECTOR}; // we should remove the prefix from `full_table_name` - let mut connect_properties = source.with_properties.clone(); - if let Some(connector) = source.with_properties.get(UPSTREAM_SOURCE_KEY) { + let mut connect_properties = source_with_properties.clone(); + if let Some(connector) = source_with_properties.get(UPSTREAM_SOURCE_KEY) { let table_name = match connector.as_str() { MYSQL_CDC_CONNECTOR => { let db_name = connect_properties.get(DATABASE_NAME_KEY).ok_or_else(|| { @@ -937,14 +873,13 @@ fn derive_connect_properties( }; connect_properties.insert(TABLE_NAME_KEY.into(), table_name.into()); } - Ok(connect_properties.into_iter().collect()) + Ok(connect_properties) } #[allow(clippy::too_many_arguments)] pub(super) async fn handle_create_table_plan( handler_args: HandlerArgs, explain_options: ExplainOptions, - col_id_gen: ColumnIdGenerator, source_schema: Option, cdc_table_info: Option, table_name: ObjectName, @@ -957,10 +892,12 @@ pub(super) async fn handle_create_table_plan( with_version_column: Option, include_column_options: IncludeOption, ) -> Result<(PlanRef, Option, PbTable, TableJobType)> { + let col_id_gen = ColumnIdGenerator::new_initial(); let source_schema = check_create_table_with_source( &handler_args.with_options, source_schema, &include_column_options, + &cdc_table_info, )?; let ((plan, source, table), job_type) = @@ -986,34 +923,82 @@ pub(super) async fn handle_create_table_plan( ), (None, None) => { let context = OptimizerContext::new(handler_args, explain_options); - ( - gen_create_table_plan( - context, - table_name.clone(), - column_defs, - constraints, - col_id_gen, - source_watermarks, - append_only, - on_conflict, - with_version_column, - )?, - TableJobType::General, - ) + let (plan, table) = gen_create_table_plan( + context, + table_name.clone(), + column_defs, + constraints, + col_id_gen, + source_watermarks, + append_only, + on_conflict, + with_version_column, + )?; + + ((plan, None, table), TableJobType::General) } (None, Some(cdc_table)) => { - let context = OptimizerContext::new(handler_args, explain_options); - let (plan, table) = gen_create_table_plan_for_cdc_source( - context.into(), - cdc_table.source_name.clone(), - table_name.clone(), + sanity_check_for_cdc_table( + append_only, + &column_defs, + &wildcard_idx, + &constraints, + &source_watermarks, + )?; + + let session = &handler_args.session; + let db_name = session.database(); + let (schema_name, resolved_table_name) = + Binder::resolve_schema_qualified_name(db_name, table_name)?; + let (database_id, schema_id) = + session.get_database_and_schema_id_for_create(schema_name.clone())?; + + // cdc table cannot be append-only + let (source_schema, source_name) = + Binder::resolve_schema_qualified_name(db_name, cdc_table.source_name.clone())?; + + let source = { + let catalog_reader = session.env().catalog_reader().read_guard(); + let schema_name = source_schema + .clone() + .unwrap_or(DEFAULT_SCHEMA_NAME.to_string()); + let (source, _) = catalog_reader.get_source_by_name( + db_name, + SchemaPath::Name(schema_name.as_str()), + source_name.as_str(), + )?; + source.clone() + }; + let connect_properties = derive_connect_properties( + &source.with_properties, cdc_table.external_table_name.clone(), - column_defs, - constraints, + )?; + + let (columns, pk_names) = derive_schema_for_cdc_table( + &column_defs, + &constraints, + connect_properties.clone(), + wildcard_idx.is_some(), + ) + .await?; + + let (plan, table) = gen_create_table_plan_for_cdc_table( + handler_args, + explain_options, + source, + cdc_table.external_table_name.clone(), + columns, + pk_names, + connect_properties, col_id_gen, on_conflict, with_version_column, + include_column_options, + resolved_table_name, + database_id, + schema_id, + TableId::placeholder(), )?; ((plan, None, table), TableJobType::SharedCdcSource) @@ -1028,6 +1013,110 @@ pub(super) async fn handle_create_table_plan( Ok((plan, source, table, job_type)) } +fn sanity_check_for_cdc_table( + append_only: bool, + column_defs: &Vec, + wildcard_idx: &Option, + constraints: &Vec, + source_watermarks: &Vec, +) -> Result<()> { + for c in column_defs { + for op in &c.options { + if let ColumnOption::GeneratedColumns(_) = op.option { + return Err(ErrorCode::NotSupported( + "generated column defined on the table created from a CDC source".into(), + "Remove the generated column in the column list".into(), + ) + .into()); + } + } + } + + // wildcard cannot be used with column definitions + if wildcard_idx.is_some() && !column_defs.is_empty() { + return Err(ErrorCode::NotSupported( + "wildcard(*) and column definitions cannot be used together".to_owned(), + "Remove the wildcard or column definitions".to_owned(), + ) + .into()); + } + + // cdc table must have primary key constraint or primary key column + if !wildcard_idx.is_some() + && !constraints.iter().any(|c| { + matches!( + c, + TableConstraint::Unique { + is_primary: true, + .. + } + ) + }) + && !column_defs.iter().any(|col| { + col.options + .iter() + .any(|opt| matches!(opt.option, ColumnOption::Unique { is_primary: true })) + }) + { + return Err(ErrorCode::NotSupported( + "CDC table without primary key constraint is not supported".to_owned(), + "Please define a primary key".to_owned(), + ) + .into()); + } + if append_only { + return Err(ErrorCode::NotSupported( + "append only modifier on the table created from a CDC source".into(), + "Remove the APPEND ONLY clause".into(), + ) + .into()); + } + + if !source_watermarks.is_empty() { + return Err(ErrorCode::NotSupported( + "watermark defined on the table created from a CDC source".into(), + "Remove the Watermark definitions".into(), + ) + .into()); + } + + Ok(()) +} + +async fn derive_schema_for_cdc_table( + column_defs: &Vec, + constraints: &Vec, + connect_properties: BTreeMap, + need_auto_schema_map: bool, +) -> Result<(Vec, Vec)> { + // read cdc table schema from external db or parsing the schema from SQL definitions + if need_auto_schema_map { + let config = ExternalTableConfig::try_from_btreemap(connect_properties) + .context("failed to extract external table config")?; + + let table = ExternalTableImpl::connect(config) + .await + .context("failed to auto derive table schema")?; + Ok(( + table + .column_descs() + .iter() + .cloned() + .map(|column_desc| ColumnCatalog { + column_desc, + is_hidden: false, + }) + .collect(), + table.pk_names().clone(), + )) + } else { + Ok(( + bind_sql_columns(column_defs)?, + bind_sql_pk_names(column_defs, constraints)?, + )) + } +} + #[allow(clippy::too_many_arguments)] pub async fn handle_create_table( handler_args: HandlerArgs, @@ -1059,11 +1148,9 @@ pub async fn handle_create_table( } let (graph, source, table, job_type) = { - let col_id_gen = ColumnIdGenerator::new_initial(); let (plan, source, table, job_type) = handle_create_table_plan( handler_args, ExplainOptions::default(), - col_id_gen, source_schema, cdc_table_info, table_name.clone(), @@ -1107,7 +1194,12 @@ pub fn check_create_table_with_source( with_options: &WithOptions, source_schema: Option, include_column_options: &IncludeOption, + cdc_table_info: &Option, ) -> Result> { + // skip check for cdc table + if cdc_table_info.is_some() { + return Ok(source_schema); + } let defined_source = with_options.inner().contains_key(UPSTREAM_SOURCE_KEY); if !include_column_options.is_empty() && !defined_source { return Err(ErrorCode::InvalidInputSyntax( @@ -1131,23 +1223,24 @@ pub async fn generate_stream_graph_for_table( source_schema: Option, handler_args: HandlerArgs, col_id_gen: ColumnIdGenerator, - columns: Vec, + column_defs: Vec, wildcard_idx: Option, constraints: Vec, source_watermarks: Vec, append_only: bool, on_conflict: Option, with_version_column: Option, -) -> Result<(StreamFragmentGraph, Table, Option)> { + cdc_table_info: Option, +) -> Result<(StreamFragmentGraph, Table, Option, TableJobType)> { use risingwave_pb::catalog::table::OptionalAssociatedSourceId; - let (plan, source, table) = match source_schema { - Some(source_schema) => { + let ((plan, source, table), job_type) = match (source_schema, cdc_table_info.as_ref()) { + (Some(source_schema), None) => ( gen_create_table_plan_with_source( handler_args, ExplainOptions::default(), table_name, - columns, + column_defs, wildcard_idx, constraints, source_schema, @@ -1158,21 +1251,69 @@ pub async fn generate_stream_graph_for_table( with_version_column, vec![], ) - .await? - } - None => { + .await?, + TableJobType::General, + ), + (None, None) => { let context = OptimizerContext::from_handler_args(handler_args); - gen_create_table_plan( + let (plan, table) = gen_create_table_plan( context, table_name, - columns, + column_defs, constraints, col_id_gen, source_watermarks, append_only, on_conflict, with_version_column, - )? + )?; + ((plan, None, table), TableJobType::General) + } + (None, Some(cdc_table)) => { + let session = &handler_args.session; + let (source, resolved_table_name, database_id, schema_id) = + get_source_and_resolved_table_name(session, cdc_table.clone(), table_name.clone())?; + + let connect_properties = derive_connect_properties( + &source.with_properties, + cdc_table.external_table_name.clone(), + )?; + + let (columns, pk_names) = derive_schema_for_cdc_table( + &column_defs, + &constraints, + connect_properties.clone(), + false, + ) + .await?; + + let (plan, table) = gen_create_table_plan_for_cdc_table( + handler_args, + ExplainOptions::default(), + source, + cdc_table.external_table_name.clone(), + columns, + pk_names, + connect_properties, + col_id_gen, + on_conflict, + with_version_column, + vec![], // empty include options + resolved_table_name, + database_id, + schema_id, + original_catalog.id(), + )?; + + ((plan, None, table), TableJobType::SharedCdcSource) + } + (Some(_), Some(_)) => { + return Err(ErrorCode::NotSupported( + "Data format and encoding format doesn't apply to table created from a CDC source" + .into(), + "Remove the FORMAT and ENCODE specification".into(), + ) + .into()) } }; @@ -1202,20 +1343,43 @@ pub async fn generate_stream_graph_for_table( ..table }; - Ok((graph, table, source)) + Ok((graph, table, source, job_type)) } -#[cfg(test)] -mod tests { - use std::collections::HashMap; +fn get_source_and_resolved_table_name( + session: &Arc, + cdc_table: CdcTableInfo, + table_name: ObjectName, +) -> Result<(Arc, String, DatabaseId, SchemaId)> { + let db_name = session.database(); + let (schema_name, resolved_table_name) = + Binder::resolve_schema_qualified_name(db_name, table_name)?; + let (database_id, schema_id) = + session.get_database_and_schema_id_for_create(schema_name.clone())?; + + let (source_schema, source_name) = + Binder::resolve_schema_qualified_name(db_name, cdc_table.source_name.clone())?; - use risingwave_common::catalog::{ - Field, DEFAULT_DATABASE_NAME, DEFAULT_SCHEMA_NAME, ROWID_PREFIX, + let source = { + let catalog_reader = session.env().catalog_reader().read_guard(); + let schema_name = source_schema.unwrap_or(DEFAULT_SCHEMA_NAME.to_string()); + let (source, _) = catalog_reader.get_source_by_name( + db_name, + SchemaPath::Name(schema_name.as_str()), + source_name.as_str(), + )?; + source.clone() }; + + Ok((source, resolved_table_name, database_id, schema_id)) +} + +#[cfg(test)] +mod tests { + use risingwave_common::catalog::{Field, DEFAULT_DATABASE_NAME, ROWID_PREFIX}; use risingwave_common::types::DataType; use super::*; - use crate::catalog::root_catalog::SchemaPath; use crate::test_utils::{create_proto_file, LocalFrontend, PROTO_FILE_DATA}; #[test] @@ -1265,7 +1429,7 @@ mod tests { // Check table exists. let (table, _) = catalog_reader - .get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t") + .get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t") .unwrap(); assert_eq!(table.name(), "t"); @@ -1345,7 +1509,8 @@ mod tests { } ensure_table_constraints_supported(&constraints)?; let pk_names = bind_sql_pk_names(&column_defs, &constraints)?; - let (_, pk_column_ids, _) = bind_pk_on_relation(columns, pk_names, true)?; + let (_, pk_column_ids, _) = + bind_pk_and_row_id_on_relation(columns, pk_names, true)?; Ok(pk_column_ids) })(); match (expected, actual) { @@ -1432,6 +1597,6 @@ mod tests { ); // Options are not merged into props. - assert!(source.with_properties.get("schema.location").is_none()); + assert!(!source.with_properties.contains_key("schema.location")); } } diff --git a/src/frontend/src/handler/create_table_as.rs b/src/frontend/src/handler/create_table_as.rs index a828cb9708b48..9a01d2919086e 100644 --- a/src/frontend/src/handler/create_table_as.rs +++ b/src/frontend/src/handler/create_table_as.rs @@ -22,7 +22,7 @@ use risingwave_sqlparser::ast::{ColumnDef, ObjectName, OnConflict, Query, Statem use super::{HandlerArgs, RwPgResponse}; use crate::binder::BoundStatement; use crate::error::{ErrorCode, Result}; -use crate::handler::create_table::{gen_create_table_plan_without_bind, ColumnIdGenerator}; +use crate::handler::create_table::{gen_create_table_plan_without_source, ColumnIdGenerator}; use crate::handler::query::handle_query; use crate::{build_graph, Binder, OptimizerContext}; pub async fn handle_create_as( @@ -90,19 +90,12 @@ pub async fn handle_create_as( let (graph, source, table) = { let context = OptimizerContext::from_handler_args(handler_args.clone()); - let properties = handler_args - .with_options - .inner() - .clone() - .into_iter() - .collect(); - let (plan, source, table) = gen_create_table_plan_without_bind( + let (plan, table) = gen_create_table_plan_without_source( context, table_name.clone(), columns, vec![], vec![], - properties, "".to_owned(), // TODO: support `SHOW CREATE TABLE` for `CREATE TABLE AS` vec![], // No watermark should be defined in for `CREATE TABLE AS` append_only, @@ -118,7 +111,7 @@ pub async fn handle_create_as( .map(|parallelism| Parallelism { parallelism: parallelism.get(), }); - (graph, source, table) + (graph, None, table) }; tracing::trace!( diff --git a/src/frontend/src/handler/declare_cursor.rs b/src/frontend/src/handler/declare_cursor.rs index 27f029bf3e4a5..25e146fa714ce 100644 --- a/src/frontend/src/handler/declare_cursor.rs +++ b/src/frontend/src/handler/declare_cursor.rs @@ -18,7 +18,7 @@ use risingwave_common::util::epoch::Epoch; use risingwave_sqlparser::ast::{DeclareCursorStatement, ObjectName, Query, Since, Statement}; use super::query::{gen_batch_plan_by_statement, gen_batch_plan_fragmenter}; -use super::util::{convert_epoch_to_logstore_i64, convert_unix_millis_to_logstore_i64}; +use super::util::convert_unix_millis_to_logstore_u64; use super::RwPgResponse; use crate::error::{ErrorCode, Result}; use crate::handler::query::create_stream; @@ -61,16 +61,16 @@ async fn handle_declare_subscription_cursor( // Start the first query of cursor, which includes querying the table and querying the subscription's logstore let start_rw_timestamp = match rw_timestamp { Some(risingwave_sqlparser::ast::Since::TimestampMsNum(start_rw_timestamp)) => { - check_cursor_unix_millis(start_rw_timestamp, subscription.get_retention_seconds()?)?; - Some(convert_unix_millis_to_logstore_i64(start_rw_timestamp)) - } - Some(risingwave_sqlparser::ast::Since::ProcessTime) => { - Some(convert_epoch_to_logstore_i64(Epoch::now().0)) + check_cursor_unix_millis(start_rw_timestamp, subscription.retention_seconds)?; + Some(convert_unix_millis_to_logstore_u64(start_rw_timestamp)) } + Some(risingwave_sqlparser::ast::Since::ProcessTime) => Some(Epoch::now().0), Some(risingwave_sqlparser::ast::Since::Begin) => { let min_unix_millis = - Epoch::now().as_unix_millis() - subscription.get_retention_seconds()? * 1000; - Some(convert_unix_millis_to_logstore_i64(min_unix_millis)) + Epoch::now().as_unix_millis() - subscription.retention_seconds * 1000; + let subscription_build_millis = subscription.created_at_epoch.unwrap().as_unix_millis(); + let min_unix_millis = std::cmp::max(min_unix_millis, subscription_build_millis); + Some(convert_unix_millis_to_logstore_u64(min_unix_millis)) } None => None, }; @@ -80,6 +80,7 @@ async fn handle_declare_subscription_cursor( .add_subscription_cursor( cursor_name.clone(), start_rw_timestamp, + subscription.dependent_table_id, subscription, &handle_args, ) @@ -111,7 +112,7 @@ async fn handle_declare_query_cursor( query: Box, ) -> Result { let (row_stream, pg_descs) = - create_stream_for_cursor(handle_args.clone(), Statement::Query(query)).await?; + create_stream_for_cursor_stmt(handle_args.clone(), Statement::Query(query)).await?; handle_args .session .get_cursor_manager() @@ -120,7 +121,7 @@ async fn handle_declare_query_cursor( Ok(PgResponse::empty_result(StatementType::DECLARE_CURSOR)) } -pub async fn create_stream_for_cursor( +pub async fn create_stream_for_cursor_stmt( handle_args: HandlerArgs, stmt: Statement, ) -> Result<(PgResponseStream, Vec)> { diff --git a/src/frontend/src/handler/drop_function.rs b/src/frontend/src/handler/drop_function.rs index 61eaa8d0c0ad0..945cf4816bf9e 100644 --- a/src/frontend/src/handler/drop_function.rs +++ b/src/frontend/src/handler/drop_function.rs @@ -12,23 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -use pgwire::pg_response::StatementType; -use risingwave_sqlparser::ast::{FunctionDesc, ReferentialAction}; - use super::*; use crate::catalog::root_catalog::SchemaPath; use crate::catalog::CatalogError; use crate::{bind_data_type, Binder}; +/// Drop a function or an aggregate. pub async fn handle_drop_function( handler_args: HandlerArgs, if_exists: bool, mut func_desc: Vec, _option: Option, + aggregate: bool, ) -> Result { if func_desc.len() != 1 { bail_not_implemented!("only support dropping 1 function"); } + let stmt_type = if aggregate { + StatementType::DROP_AGGREGATE + } else { + StatementType::DROP_FUNCTION + }; let func_desc = func_desc.remove(0); let session = handler_args.session; @@ -73,10 +77,21 @@ pub async fn handle_drop_function( match res { Ok((function, schema_name)) => { session.check_privilege_for_drop_alter(schema_name, &**function)?; + if !aggregate && function.kind.is_aggregate() { + return Err(ErrorCode::CatalogError( + format!("\"{function_name}\" is an aggregate function\nHINT: Use DROP AGGREGATE to drop aggregate functions.").into(), + ) + .into()); + } else if aggregate && !function.kind.is_aggregate() { + return Err(ErrorCode::CatalogError( + format!("\"{function_name}\" is not an aggregate").into(), + ) + .into()); + } function.id } Err(CatalogError::NotFound(kind, _)) if kind == "function" && if_exists => { - return Ok(RwPgResponse::builder(StatementType::DROP_FUNCTION) + return Ok(RwPgResponse::builder(stmt_type) .notice(format!( "function \"{}\" does not exist, skipping", function_name @@ -90,5 +105,5 @@ pub async fn handle_drop_function( let catalog_writer = session.catalog_writer()?; catalog_writer.drop_function(function_id).await?; - Ok(PgResponse::empty_result(StatementType::DROP_FUNCTION)) + Ok(PgResponse::empty_result(stmt_type)) } diff --git a/src/frontend/src/handler/drop_index.rs b/src/frontend/src/handler/drop_index.rs index 9dc524f13df5c..ec2d19722eefd 100644 --- a/src/frontend/src/handler/drop_index.rs +++ b/src/frontend/src/handler/drop_index.rs @@ -54,7 +54,7 @@ pub async fn handle_drop_index( } _ => return Err(err.into()), }; - return match reader.get_table_by_name(db_name, schema_path, &index_name) { + return match reader.get_created_table_by_name(db_name, schema_path, &index_name) { Ok((table, _)) => match table.table_type() { TableType::Index => unreachable!(), _ => Err(table.bad_drop_error()), @@ -108,7 +108,8 @@ mod tests { let catalog_reader = session.env().catalog_reader().read_guard(); let schema_path = SchemaPath::Name(DEFAULT_SCHEMA_NAME); - let table = catalog_reader.get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "idx"); + let table = + catalog_reader.get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "idx"); assert!(table.is_err()); } } diff --git a/src/frontend/src/handler/drop_mv.rs b/src/frontend/src/handler/drop_mv.rs index f727eb5c8dd51..9ec6a56d20d3b 100644 --- a/src/frontend/src/handler/drop_mv.rs +++ b/src/frontend/src/handler/drop_mv.rs @@ -13,6 +13,8 @@ // limitations under the License. use pgwire::pg_response::{PgResponse, StatementType}; +use risingwave_common::catalog::StreamJobStatus; +use risingwave_pb::meta::cancel_creating_jobs_request::{CreatingJobIds, PbJobs}; use risingwave_sqlparser::ast::ObjectName; use super::RwPgResponse; @@ -37,10 +39,10 @@ pub async fn handle_drop_mv( let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name); - let table_id = { + let (table_id, status) = { let reader = session.env().catalog_reader().read_guard(); let (table, schema_name) = - match reader.get_table_by_name(session.database(), schema_path, &table_name) { + match reader.get_any_table_by_name(session.database(), schema_path, &table_name) { Ok((t, s)) => (t, s), Err(e) => { return if if_exists { @@ -68,13 +70,27 @@ pub async fn handle_drop_mv( _ => return Err(table.bad_drop_error()), } - table.id() + (table.id(), table.stream_job_status) }; - let catalog_writer = session.catalog_writer()?; - catalog_writer - .drop_materialized_view(table_id, cascade) - .await?; + match status { + StreamJobStatus::Created => { + let catalog_writer = session.catalog_writer()?; + catalog_writer + .drop_materialized_view(table_id, cascade) + .await?; + } + StreamJobStatus::Creating => { + let canceled_jobs = session + .env() + .meta_client() + .cancel_creating_jobs(PbJobs::Ids(CreatingJobIds { + job_ids: vec![table_id.table_id], + })) + .await?; + tracing::info!(?canceled_jobs, "cancelled creating jobs"); + } + } Ok(PgResponse::empty_result( StatementType::DROP_MATERIALIZED_VIEW, @@ -102,7 +118,8 @@ mod tests { let catalog_reader = session.env().catalog_reader().read_guard(); let schema_path = SchemaPath::Name(DEFAULT_SCHEMA_NAME); - let table = catalog_reader.get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "mv"); + let table = + catalog_reader.get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "mv"); assert!(table.is_err()); } } diff --git a/src/frontend/src/handler/drop_secret.rs b/src/frontend/src/handler/drop_secret.rs new file mode 100644 index 0000000000000..37fbd2cedd408 --- /dev/null +++ b/src/frontend/src/handler/drop_secret.rs @@ -0,0 +1,64 @@ +// Copyright 2024 RisingWave Labs +// +// 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 pgwire::pg_response::StatementType; +use risingwave_sqlparser::ast::ObjectName; + +use crate::catalog::root_catalog::SchemaPath; +use crate::error::Result; +use crate::handler::{HandlerArgs, RwPgResponse}; +use crate::Binder; + +pub async fn handle_drop_secret( + handler_args: HandlerArgs, + secret_name: ObjectName, + if_exists: bool, +) -> Result { + let session = handler_args.session; + let db_name = session.database(); + let (schema_name, secret_name) = Binder::resolve_schema_qualified_name(db_name, secret_name)?; + let search_path = session.config().search_path(); + let user_name = &session.auth_context().user_name; + let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name); + + let secret_id = { + let reader = session.env().catalog_reader().read_guard(); + let (secret, schema_name) = + match reader.get_secret_by_name(db_name, schema_path, secret_name.as_str()) { + Ok((c, s)) => (c, s), + Err(e) => { + return if if_exists { + Ok(RwPgResponse::builder(StatementType::DROP_SECRET) + .notice(format!( + "secret \"{}\" does not exist, skipping", + secret_name + )) + .into()) + } else { + Err(e.into()) + }; + } + }; + session.check_privilege_for_drop_alter(schema_name, &**secret)?; + + secret.secret_id + }; + + let catalog_writer = session.catalog_writer()?; + catalog_writer.drop_secret(secret_id).await?; + + Ok(RwPgResponse::builder(StatementType::DROP_SECRET) + .notice(format!("dropped secret \"{}\"", secret_name)) + .into()) +} diff --git a/src/frontend/src/handler/drop_sink.rs b/src/frontend/src/handler/drop_sink.rs index a42605ad1c856..a209a16c88ae3 100644 --- a/src/frontend/src/handler/drop_sink.rs +++ b/src/frontend/src/handler/drop_sink.rs @@ -13,7 +13,7 @@ // limitations under the License. use pgwire::pg_response::{PgResponse, StatementType}; -use risingwave_pb::ddl_service::ReplaceTablePlan; +use risingwave_pb::ddl_service::{ReplaceTablePlan, TableJobType}; use risingwave_sqlparser::ast::ObjectName; use super::RwPgResponse; @@ -63,7 +63,7 @@ pub async fn handle_drop_sink( if let Some(target_table_id) = &sink.target_table { let table_catalog = { let reader = session.env().catalog_reader().read_guard(); - let table = reader.get_table_by_id(target_table_id)?; + let table = reader.get_any_table_by_id(target_table_id)?; table.clone() }; @@ -89,6 +89,7 @@ pub async fn handle_drop_sink( table: Some(table), fragment_graph: Some(graph), table_col_index_mapping: None, + job_type: TableJobType::General as _, }); } @@ -123,7 +124,8 @@ mod tests { let catalog_reader = session.env().catalog_reader().read_guard(); let schema_path = SchemaPath::Name(DEFAULT_SCHEMA_NAME); - let sink = catalog_reader.get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "snk"); + let sink = + catalog_reader.get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "snk"); assert!(sink.is_err()); } } diff --git a/src/frontend/src/handler/drop_source.rs b/src/frontend/src/handler/drop_source.rs index 0003d3a6ce5d0..fa5c5d7b9b1fb 100644 --- a/src/frontend/src/handler/drop_source.rs +++ b/src/frontend/src/handler/drop_source.rs @@ -38,7 +38,8 @@ pub async fn handle_drop_source( let (source, schema_name) = { let catalog_reader = session.env().catalog_reader().read_guard(); - if let Ok((table, _)) = catalog_reader.get_table_by_name(db_name, schema_path, &source_name) + if let Ok((table, _)) = + catalog_reader.get_created_table_by_name(db_name, schema_path, &source_name) { return Err(table.bad_drop_error()); } diff --git a/src/frontend/src/handler/drop_table.rs b/src/frontend/src/handler/drop_table.rs index d0fbc3d4e0f78..2f85eb98d704b 100644 --- a/src/frontend/src/handler/drop_table.rs +++ b/src/frontend/src/handler/drop_table.rs @@ -38,19 +38,19 @@ pub async fn handle_drop_table( let (source_id, table_id) = { let reader = session.env().catalog_reader().read_guard(); - let (table, schema_name) = match reader.get_table_by_name(db_name, schema_path, &table_name) - { - Ok((t, s)) => (t, s), - Err(e) => { - return if if_exists { - Ok(RwPgResponse::builder(StatementType::DROP_TABLE) - .notice(format!("table \"{}\" does not exist, skipping", table_name)) - .into()) - } else { - Err(e.into()) + let (table, schema_name) = + match reader.get_created_table_by_name(db_name, schema_path, &table_name) { + Ok((t, s)) => (t, s), + Err(e) => { + return if if_exists { + Ok(RwPgResponse::builder(StatementType::DROP_TABLE) + .notice(format!("table \"{}\" does not exist, skipping", table_name)) + .into()) + } else { + Err(e.into()) + } } - } - }; + }; session.check_privilege_for_drop_alter(schema_name, &**table)?; @@ -91,7 +91,8 @@ mod tests { let source = catalog_reader.get_source_by_name(DEFAULT_DATABASE_NAME, schema_path, "t"); assert!(source.is_err()); - let table = catalog_reader.get_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t"); + let table = + catalog_reader.get_created_table_by_name(DEFAULT_DATABASE_NAME, schema_path, "t"); assert!(table.is_err()); } } diff --git a/src/frontend/src/handler/explain.rs b/src/frontend/src/handler/explain.rs index cfc416f0d8b0d..db124b373181b 100644 --- a/src/frontend/src/handler/explain.rs +++ b/src/frontend/src/handler/explain.rs @@ -22,8 +22,6 @@ use thiserror_ext::AsReport; use super::create_index::{gen_create_index_plan, resolve_index_schema}; use super::create_mv::gen_create_mv_plan; use super::create_sink::{gen_sink_plan, get_partition_compute_info}; -use super::create_subscription::gen_subscription_plan; -use super::create_table::ColumnIdGenerator; use super::query::gen_batch_plan_by_statement; use super::util::SourceSchemaCompatExt; use super::{RwPgResponse, RwPgResponseBuilderExt}; @@ -67,14 +65,11 @@ async fn do_handle_explain( wildcard_idx, .. } => { - let col_id_gen = ColumnIdGenerator::new_initial(); - let source_schema = source_schema.map(|s| s.into_v2_with_warning()); let (plan, _source, _table, _job_type) = handle_create_table_plan( handler_args, explain_options, - col_id_gen, source_schema, cdc_table_info, name.clone(), @@ -135,9 +130,11 @@ async fn do_handle_explain( ).into()); } - Statement::CreateSubscription { stmt } => { - gen_subscription_plan(&session, context.clone(), stmt) - .map(|plan| plan.subscription_plan) + Statement::CreateSubscription { .. } => { + return Err(ErrorCode::NotSupported( + "EXPLAIN CREATE SUBSCRIPTION".into(), + "A created SUBSCRIPTION only incremental data queries on the table, not supported EXPLAIN".into() + ).into()); } Statement::CreateIndex { name, diff --git a/src/frontend/src/handler/handle_privilege.rs b/src/frontend/src/handler/handle_privilege.rs index 44e5b8fdac33c..851797c7265b3 100644 --- a/src/frontend/src/handler/handle_privilege.rs +++ b/src/frontend/src/handler/handle_privilege.rs @@ -21,6 +21,7 @@ use super::RwPgResponse; use crate::binder::Binder; use crate::catalog::root_catalog::SchemaPath; use crate::catalog::table_catalog::TableType; +use crate::catalog::CatalogError; use crate::error::{ErrorCode, Result}; use crate::handler::HandlerArgs; use crate::session::SessionImpl; @@ -70,7 +71,8 @@ fn make_prost_privilege( Binder::resolve_schema_qualified_name(db_name, name)?; let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name); - let (table, _) = reader.get_table_by_name(db_name, schema_path, &table_name)?; + let (table, _) = + reader.get_created_table_by_name(db_name, schema_path, &table_name)?; match table.table_type() { TableType::MaterializedView => {} _ => { @@ -93,17 +95,31 @@ fn make_prost_privilege( Binder::resolve_schema_qualified_name(db_name, name)?; let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name); - let (table, _) = reader.get_table_by_name(db_name, schema_path, &table_name)?; - match table.table_type() { - TableType::Table => {} - _ => { - return Err(ErrorCode::InvalidInputSyntax(format!( - "{table_name} is not a table", - )) - .into()); + match reader.get_created_table_by_name(db_name, schema_path, &table_name) { + Ok((table, _)) => { + match table.table_type() { + TableType::Table => { + grant_objs.push(PbObject::TableId(table.id().table_id)); + continue; + } + _ => { + return Err(ErrorCode::InvalidInputSyntax(format!( + "{table_name} is not a table", + )) + .into()); + } + }; + } + Err(CatalogError::NotFound("table", _)) => { + let (view, _) = reader + .get_view_by_name(db_name, schema_path, &table_name) + .map_err(|_| CatalogError::NotFound("table", table_name))?; + grant_objs.push(PbObject::ViewId(view.id)); + } + Err(e) => { + return Err(e.into()); } } - grant_objs.push(PbObject::TableId(table.id().table_id)); } } GrantObjects::Sources(sources) => { @@ -138,7 +154,7 @@ fn make_prost_privilege( for schema in schemas { let schema_name = Binder::resolve_schema_name(schema)?; let schema = reader.get_schema_by_name(session.database(), &schema_name)?; - grant_objs.push(PbObject::AllDmlTablesSchemaId(schema.id())); + grant_objs.push(PbObject::AllDmlRelationsSchemaId(schema.id())); } } o => { diff --git a/src/frontend/src/handler/mod.rs b/src/frontend/src/handler/mod.rs index 80c91485c7a74..f8beeedb19438 100644 --- a/src/frontend/src/handler/mod.rs +++ b/src/frontend/src/handler/mod.rs @@ -27,6 +27,7 @@ use pgwire::types::{Format, Row}; use risingwave_common::bail_not_implemented; use risingwave_common::types::Fields; use risingwave_common::util::iter_util::ZipEqFast; +use risingwave_pb::meta::PbThrottleTarget; use risingwave_sqlparser::ast::*; use self::util::{DataChunkToRowSetAdapter, SourceSchemaCompatExt}; @@ -44,6 +45,7 @@ mod alter_rename; mod alter_set_schema; mod alter_source_column; mod alter_source_with_sr; +mod alter_streaming_rate_limit; mod alter_system; mod alter_table_column; mod alter_table_with_sr; @@ -51,12 +53,14 @@ pub mod alter_user; pub mod cancel_job; pub mod close_cursor; mod comment; +pub mod create_aggregate; pub mod create_connection; mod create_database; pub mod create_function; pub mod create_index; pub mod create_mv; pub mod create_schema; +pub mod create_secret; pub mod create_sink; pub mod create_source; pub mod create_sql_function; @@ -74,6 +78,7 @@ pub mod drop_function; mod drop_index; pub mod drop_mv; mod drop_schema; +pub mod drop_secret; pub mod drop_sink; pub mod drop_source; pub mod drop_subscription; @@ -254,6 +259,9 @@ pub async fn handle( Statement::CreateConnection { stmt } => { create_connection::handle_create_connection(handler_args, stmt).await } + Statement::CreateSecret { stmt } => { + create_secret::handle_create_secret(handler_args, stmt).await + } Statement::CreateFunction { or_replace, temporary, @@ -273,7 +281,6 @@ pub async fn handle( .real_value() .eq_ignore_ascii_case("sql") { - // User defined function with external source (e.g., language [ python / java ]) create_function::handle_create_function( handler_args, or_replace, @@ -298,6 +305,24 @@ pub async fn handle( .await } } + Statement::CreateAggregate { + or_replace, + name, + args, + returns, + params, + .. + } => { + create_aggregate::handle_create_aggregate( + handler_args, + or_replace, + name, + args, + returns, + params, + ) + .await + } Statement::CreateTable { name, columns, @@ -361,7 +386,16 @@ pub async fn handle( Statement::CreateSchema { schema_name, if_not_exists, - } => create_schema::handle_create_schema(handler_args, schema_name, if_not_exists).await, + user_specified, + } => { + create_schema::handle_create_schema( + handler_args, + schema_name, + if_not_exists, + user_specified, + ) + .await + } Statement::CreateUser(stmt) => create_user::handle_create_user(handler_args, stmt).await, Statement::DeclareCursor { stmt } => { declare_cursor::handle_declare_cursor(handler_args, stmt).await @@ -412,7 +446,8 @@ pub async fn handle( ObjectType::Schema | ObjectType::Database | ObjectType::User - | ObjectType::Connection => { + | ObjectType::Connection + | ObjectType::Secret => { bail_not_implemented!("DROP CASCADE"); } }; @@ -479,6 +514,9 @@ pub async fn handle( drop_connection::handle_drop_connection(handler_args, object_name, if_exists) .await } + ObjectType::Secret => { + drop_secret::handle_drop_secret(handler_args, object_name, if_exists).await + } } } // XXX: should we reuse Statement::Drop for DROP FUNCTION? @@ -486,7 +524,18 @@ pub async fn handle( if_exists, func_desc, option, - } => drop_function::handle_drop_function(handler_args, if_exists, func_desc, option).await, + } => { + drop_function::handle_drop_function(handler_args, if_exists, func_desc, option, false) + .await + } + Statement::DropAggregate { + if_exists, + func_desc, + option, + } => { + drop_function::handle_drop_function(handler_args, if_exists, func_desc, option, true) + .await + } Statement::Query(_) | Statement::Insert { .. } | Statement::Delete { .. } @@ -646,6 +695,18 @@ pub async fn handle( name, operation: AlterTableOperation::RefreshSchema, } => alter_table_with_sr::handle_refresh_schema(handler_args, name).await, + Statement::AlterTable { + name, + operation: AlterTableOperation::SetStreamingRateLimit { rate_limit }, + } => { + alter_streaming_rate_limit::handle_alter_streaming_rate_limit( + handler_args, + PbThrottleTarget::TableWithSource, + name, + rate_limit, + ) + .await + } Statement::AlterIndex { name, operation: AlterIndexOperation::RenameIndex { index_name }, @@ -750,6 +811,19 @@ pub async fn handle( .await } } + Statement::AlterView { + materialized, + name, + operation: AlterViewOperation::SetStreamingRateLimit { rate_limit }, + } if materialized => { + alter_streaming_rate_limit::handle_alter_streaming_rate_limit( + handler_args, + PbThrottleTarget::Mv, + name, + rate_limit, + ) + .await + } Statement::AlterSink { name, operation: AlterSinkOperation::RenameSink { sink_name }, @@ -826,23 +900,6 @@ pub async fn handle( ) .await } - Statement::AlterSubscription { - name, - operation: - AlterSubscriptionOperation::SetParallelism { - parallelism, - deferred, - }, - } => { - alter_parallelism::handle_alter_parallelism( - handler_args, - name, - parallelism, - StatementType::ALTER_SUBSCRIPTION, - deferred, - ) - .await - } Statement::AlterSource { name, operation: AlterSourceOperation::RenameSource { source_name }, @@ -887,6 +944,18 @@ pub async fn handle( name, operation: AlterSourceOperation::RefreshSchema, } => alter_source_with_sr::handler_refresh_schema(handler_args, name).await, + Statement::AlterSource { + name, + operation: AlterSourceOperation::SetStreamingRateLimit { rate_limit }, + } => { + alter_streaming_rate_limit::handle_alter_streaming_rate_limit( + handler_args, + PbThrottleTarget::Source, + name, + rate_limit, + ) + .await + } Statement::AlterFunction { name, args, diff --git a/src/frontend/src/handler/query.rs b/src/frontend/src/handler/query.rs index 68f0653a3588c..745749c9aef20 100644 --- a/src/frontend/src/handler/query.rs +++ b/src/frontend/src/handler/query.rs @@ -197,8 +197,8 @@ fn gen_batch_query_plan( let physical = match query_mode { QueryMode::Auto => unreachable!(), - QueryMode::Local => logical.gen_batch_local_plan(batch_plan)?, - QueryMode::Distributed => logical.gen_batch_distributed_plan(batch_plan)?, + QueryMode::Local => logical.gen_batch_local_plan()?, + QueryMode::Distributed => logical.gen_batch_distributed_plan()?, }; Ok(BatchQueryPlanResult { @@ -386,9 +386,7 @@ async fn execute( "no affected rows in output".to_string(), ))) } - Some(row) => { - row.map_err(|err| RwError::from(ErrorCode::InternalError(format!("{}", err))))? - } + Some(row) => row?, }; let affected_rows_str = first_row_set[0].values()[0] .as_ref() diff --git a/src/frontend/src/handler/show.rs b/src/frontend/src/handler/show.rs index dfeeb0965ad64..e543617f9ddcd 100644 --- a/src/frontend/src/handler/show.rs +++ b/src/frontend/src/handler/show.rs @@ -21,7 +21,8 @@ use pgwire::pg_response::{PgResponse, StatementType}; use pgwire::pg_server::Session; use risingwave_common::bail_not_implemented; use risingwave_common::catalog::{ColumnCatalog, ColumnDesc, DEFAULT_SCHEMA_NAME}; -use risingwave_common::types::{DataType, Fields}; +use risingwave_common::session_config::{SearchPath, USER_NAME_WILD_CARD}; +use risingwave_common::types::{DataType, Fields, Timestamptz}; use risingwave_common::util::addr::HostAddr; use risingwave_connector::source::kafka::PRIVATELINK_CONNECTION; use risingwave_expr::scalar::like::{i_like_default, like_default}; @@ -29,7 +30,6 @@ use risingwave_pb::catalog::connection; use risingwave_sqlparser::ast::{ display_comma_separated, Ident, ObjectName, ShowCreateType, ShowObject, ShowStatementFilter, }; -use serde_json; use super::{fields_to_descriptors, RwPgResponse, RwPgResponseBuilderExt}; use crate::binder::{Binder, Relation}; @@ -106,6 +106,28 @@ fn schema_or_default(schema: &Option) -> String { .map_or_else(|| DEFAULT_SCHEMA_NAME.to_string(), |s| s.real_value()) } +fn schema_or_search_path( + session: &Arc, + schema: &Option, + search_path: &SearchPath, +) -> Vec { + if let Some(s) = schema { + vec![s.real_value()] + } else { + search_path + .real_path() + .iter() + .map(|s| { + if s.eq(USER_NAME_WILD_CARD) { + session.auth_context().user_name.clone() + } else { + s.to_string() + } + }) + .collect() + } +} + #[derive(Fields)] #[fields(style = "Title Case")] struct ShowObjectRow { @@ -188,12 +210,15 @@ impl From> for ShowIndexRow { #[derive(Fields)] #[fields(style = "Title Case")] struct ShowClusterRow { + id: i32, addr: String, + r#type: String, state: String, parallel_units: String, - is_streaming: String, - is_serving: String, - is_unschedulable: String, + is_streaming: Option, + is_serving: Option, + is_unschedulable: Option, + started_at: Option, } #[derive(Fields)] @@ -251,12 +276,22 @@ pub async fn handle_show_object( let names = match command { // If not include schema name, use default schema name - ShowObject::Table { schema } => catalog_reader - .read_guard() - .get_schema_by_name(session.database(), &schema_or_default(&schema))? - .iter_table() - .map(|t| t.name.clone()) - .collect(), + ShowObject::Table { schema } => { + let search_path = session.config().search_path(); + let mut table_names_in_schema = vec![]; + for schema in schema_or_search_path(&session, &schema, &search_path) { + // If the schema is not found, skip it + if let Ok(schema_catalog) = catalog_reader + .read_guard() + .get_schema_by_name(session.database(), schema.as_ref()) + { + table_names_in_schema + .extend(schema_catalog.iter_table().map(|t| t.name.clone())); + } + } + + table_names_in_schema + } ShowObject::InternalTable { schema } => catalog_reader .read_guard() .get_schema_by_name(session.database(), &schema_or_default(&schema))? @@ -276,14 +311,13 @@ pub async fn handle_show_object( ShowObject::MaterializedView { schema } => catalog_reader .read_guard() .get_schema_by_name(session.database(), &schema_or_default(&schema))? - .iter_mv() + .iter_created_mvs() .map(|t| t.name.clone()) .collect(), ShowObject::Source { schema } => catalog_reader .read_guard() .get_schema_by_name(session.database(), &schema_or_default(&schema))? .iter_source() - .filter(|t| t.associated_table_id.is_none()) .map(|t| t.name.clone()) .collect(), ShowObject::Sink { schema } => catalog_reader @@ -298,6 +332,12 @@ pub async fn handle_show_object( .iter_subscription() .map(|t| t.name.clone()) .collect(), + ShowObject::Secret { schema } => catalog_reader + .read_guard() + .get_schema_by_name(session.database(), &schema_or_default(&schema))? + .iter_secret() + .map(|t| t.name.clone()) + .collect(), ShowObject::Columns { table } => { let Ok(columns) = get_columns_from_table(&session, table.clone()) .or(get_columns_from_sink(&session, table.clone())) @@ -386,17 +426,22 @@ pub async fn handle_show_object( .into()); } ShowObject::Cluster => { - let workers = session.env().worker_node_manager().list_worker_nodes(); - let rows = workers.into_iter().map(|worker| { + let workers = session.env().meta_client().list_all_nodes().await?; + let rows = workers.into_iter().sorted_by_key(|w| w.id).map(|worker| { let addr: HostAddr = worker.host.as_ref().unwrap().into(); - let property = worker.property.as_ref().unwrap(); + let property = worker.property.as_ref(); ShowClusterRow { + id: worker.id as _, addr: addr.to_string(), + r#type: worker.get_type().unwrap().as_str_name().into(), state: worker.get_state().unwrap().as_str_name().to_string(), parallel_units: worker.parallel_units.into_iter().map(|pu| pu.id).join(", "), - is_streaming: property.is_streaming.to_string(), - is_serving: property.is_serving.to_string(), - is_unschedulable: property.is_unschedulable.to_string(), + is_streaming: property.map(|p| p.is_streaming), + is_serving: property.map(|p| p.is_serving), + is_unschedulable: property.map(|p| p.is_unschedulable), + started_at: worker + .started_at + .map(|ts| Timestamptz::from_secs(ts as i64).unwrap()), } }); return Ok(PgResponse::builder(StatementType::SHOW_COMMAND) @@ -471,7 +516,7 @@ pub fn handle_show_create_object( let sql = match show_create_type { ShowCreateType::MaterializedView => { let mv = schema - .get_table_by_name(&object_name) + .get_created_table_by_name(&object_name) .filter(|t| t.is_mview()) .ok_or_else(|| CatalogError::NotFound("materialized view", name.to_string()))?; mv.create_sql() @@ -484,7 +529,7 @@ pub fn handle_show_create_object( } ShowCreateType::Table => { let table = schema - .get_table_by_name(&object_name) + .get_created_table_by_name(&object_name) .filter(|t| t.is_table()) .ok_or_else(|| CatalogError::NotFound("table", name.to_string()))?; table.create_sql() @@ -504,7 +549,7 @@ pub fn handle_show_create_object( } ShowCreateType::Index => { let index = schema - .get_table_by_name(&object_name) + .get_created_table_by_name(&object_name) .filter(|t| t.is_index()) .ok_or_else(|| CatalogError::NotFound("index", name.to_string()))?; index.create_sql() diff --git a/src/frontend/src/handler/util.rs b/src/frontend/src/handler/util.rs index 011b078958946..73b52b977c7a4 100644 --- a/src/frontend/src/handler/util.rs +++ b/src/frontend/src/handler/util.rs @@ -32,12 +32,11 @@ use risingwave_common::types::{write_date_time_tz, DataType, ScalarRefImpl, Time use risingwave_common::util::epoch::Epoch; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_sqlparser::ast::{ - BinaryOperator, CompatibleSourceSchema, ConnectorSchema, Expr, ObjectName, OrderByExpr, Query, - Select, SelectItem, SetExpr, TableFactor, TableWithJoins, Value, + CompatibleSourceSchema, ConnectorSchema, ObjectName, Query, Select, SelectItem, SetExpr, + TableFactor, TableWithJoins, }; use crate::error::{ErrorCode, Result as RwResult}; -use crate::session::cursor_manager::{KV_LOG_STORE_EPOCH, KV_LOG_STORE_SEQ_ID, KV_LOG_STORE_VNODE}; use crate::session::{current, SessionImpl}; pin_project! { @@ -152,6 +151,15 @@ fn to_pg_rows( session_data: &StaticSessionData, ) -> RwResult> { assert_eq!(chunk.dimension(), column_types.len()); + if cfg!(debug_assertions) { + let chunk_data_types = chunk.data_types(); + for (ty1, ty2) in chunk_data_types.iter().zip_eq_fast(column_types) { + debug_assert!( + ty1.equals_datatype(ty2), + "chunk_data_types: {chunk_data_types:?}, column_types: {column_types:?}" + ) + } + } chunk .rows() @@ -222,67 +230,18 @@ pub fn gen_query_from_table_name(from_name: ObjectName) -> Query { } } -pub fn gen_query_from_logstore_ge_rw_timestamp(logstore_name: &str, rw_timestamp: i64) -> Query { - let table_factor = TableFactor::Table { - name: ObjectName(vec![logstore_name.into()]), - alias: None, - as_of: None, - }; - let from = vec![TableWithJoins { - relation: table_factor, - joins: vec![], - }]; - let selection = Some(Expr::BinaryOp { - left: Box::new(Expr::Identifier(KV_LOG_STORE_EPOCH.into())), - op: BinaryOperator::GtEq, - right: Box::new(Expr::Value(Value::Number(rw_timestamp.to_string()))), - }); - let except_columns = vec![ - Expr::Identifier(KV_LOG_STORE_SEQ_ID.into()), - Expr::Identifier(KV_LOG_STORE_VNODE.into()), - ]; - let select = Select { - from, - projection: vec![SelectItem::Wildcard(Some(except_columns))], - selection, - ..Default::default() - }; - let order_by = vec![OrderByExpr { - expr: Expr::Identifier(KV_LOG_STORE_EPOCH.into()), - asc: None, - nulls_first: None, - }]; - let body = SetExpr::Select(Box::new(select)); - Query { - with: None, - body, - order_by, - limit: None, - offset: None, - fetch: None, - } -} - -pub fn convert_unix_millis_to_logstore_i64(unix_millis: u64) -> i64 { - let epoch = Epoch::from_unix_millis(unix_millis); - convert_epoch_to_logstore_i64(epoch.0) -} - -pub fn convert_epoch_to_logstore_i64(epoch: u64) -> i64 { - epoch as i64 ^ (1i64 << 63) +pub fn convert_unix_millis_to_logstore_u64(unix_millis: u64) -> u64 { + Epoch::from_unix_millis(unix_millis).0 } -pub fn convert_logstore_i64_to_unix_millis(logstore_i64: i64) -> u64 { - let epoch = Epoch::from(logstore_i64 as u64 ^ (1u64 << 63)); - epoch.as_unix_millis() +pub fn convert_logstore_u64_to_unix_millis(logstore_u64: u64) -> u64 { + Epoch::from(logstore_u64).as_unix_millis() } #[cfg(test)] mod tests { - use bytes::BytesMut; use postgres_types::{ToSql, Type}; use risingwave_common::array::*; - use risingwave_common::types::Timestamptz; use super::*; diff --git a/src/frontend/src/lib.rs b/src/frontend/src/lib.rs index 7f41de5cdc190..dbc312ac463ca 100644 --- a/src/frontend/src/lib.rs +++ b/src/frontend/src/lib.rs @@ -32,7 +32,6 @@ #![feature(impl_trait_in_assoc_type)] #![feature(result_flattening)] #![feature(error_generic_member_access)] -#![feature(round_ties_even)] #![feature(iterator_try_collect)] #![feature(used_with_arg)] #![feature(entry_insert)] @@ -43,6 +42,9 @@ risingwave_expr_impl::enable!(); #[macro_use] mod catalog; + +use std::collections::HashSet; + pub use catalog::TableCatalog; mod binder; pub use binder::{bind_data_type, Binder}; @@ -59,6 +61,7 @@ pub mod session; mod stream_fragmenter; use risingwave_common::config::{MetricLevel, OverrideConfig}; use risingwave_common::util::meta_addr::MetaAddressStrategy; +use risingwave_common::util::tokio_util::sync::CancellationToken; pub use stream_fragmenter::build_graph; mod utils; pub use utils::{explain_stream_graph, WithOptions}; @@ -90,7 +93,7 @@ pub struct FrontendOpts { // TODO: rename to listen_addr and separate out the port. /// The address that this service listens to. /// Usually the localhost + desired port. - #[clap(long, env = "RW_LISTEN_ADDR", default_value = "127.0.0.1:4566")] + #[clap(long, env = "RW_LISTEN_ADDR", default_value = "0.0.0.0:4566")] pub listen_addr: String, /// The address for contacting this instance of the service. @@ -163,14 +166,33 @@ use std::pin::Pin; use pgwire::pg_protocol::TlsConfig; /// Start frontend -pub fn start(opts: FrontendOpts) -> Pin + Send>> { +pub fn start( + opts: FrontendOpts, + shutdown: CancellationToken, +) -> Pin + Send>> { // WARNING: don't change the function signature. Making it `async fn` will cause // slow compile in release mode. Box::pin(async move { let listen_addr = opts.listen_addr.clone(); - let session_mgr = Arc::new(SessionManagerImpl::new(opts).await.unwrap()); - pg_serve(&listen_addr, session_mgr, TlsConfig::new_default()) - .await - .unwrap(); + let session_mgr = SessionManagerImpl::new(opts).await.unwrap(); + let redact_sql_option_keywords = Arc::new( + session_mgr + .env() + .batch_config() + .redact_sql_option_keywords + .iter() + .map(|s| s.to_lowercase()) + .collect::>(), + ); + + pg_serve( + &listen_addr, + session_mgr, + TlsConfig::new_default(), + Some(redact_sql_option_keywords), + shutdown, + ) + .await + .unwrap() }) } diff --git a/src/frontend/src/meta_client.rs b/src/frontend/src/meta_client.rs index 3cc7e22cf8b24..c54ebcd0aecb9 100644 --- a/src/frontend/src/meta_client.rs +++ b/src/frontend/src/meta_client.rs @@ -33,7 +33,7 @@ use risingwave_pb::meta::list_fragment_distribution_response::FragmentDistributi use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies; use risingwave_pb::meta::list_table_fragment_states_response::TableFragmentState; use risingwave_pb::meta::list_table_fragments_response::TableFragmentInfo; -use risingwave_pb::meta::EventLog; +use risingwave_pb::meta::{EventLog, PbThrottleTarget}; use risingwave_rpc_client::error::Result; use risingwave_rpc_client::{HummockMetaClient, MetaClient}; @@ -44,6 +44,8 @@ use risingwave_rpc_client::{HummockMetaClient, MetaClient}; /// in this trait so that the mocking can be simplified. #[async_trait::async_trait] pub trait FrontendMetaClient: Send + Sync { + async fn try_unregister(&self); + async fn pin_snapshot(&self) -> Result; async fn get_snapshot(&self) -> Result; @@ -117,12 +119,30 @@ pub trait FrontendMetaClient: Send + Sync { async fn list_all_nodes(&self) -> Result>; async fn list_compact_task_progress(&self) -> Result>; + + async fn apply_throttle( + &self, + kind: PbThrottleTarget, + id: u32, + rate_limit: Option, + ) -> Result<()>; + + async fn list_change_log_epochs( + &self, + table_id: u32, + min_epoch: u64, + max_count: u32, + ) -> Result>; } pub struct FrontendMetaClientImpl(pub MetaClient); #[async_trait::async_trait] impl FrontendMetaClient for FrontendMetaClientImpl { + async fn try_unregister(&self) { + self.0.try_unregister().await; + } + async fn pin_snapshot(&self) -> Result { self.0.pin_snapshot().await } @@ -293,4 +313,27 @@ impl FrontendMetaClient for FrontendMetaClientImpl { async fn list_compact_task_progress(&self) -> Result> { self.0.list_compact_task_progress().await } + + async fn apply_throttle( + &self, + kind: PbThrottleTarget, + id: u32, + rate_limit: Option, + ) -> Result<()> { + self.0 + .apply_throttle(kind, id, rate_limit) + .await + .map(|_| ()) + } + + async fn list_change_log_epochs( + &self, + table_id: u32, + min_epoch: u64, + max_count: u32, + ) -> Result> { + self.0 + .list_change_log_epochs(table_id, min_epoch, max_count) + .await + } } diff --git a/src/frontend/src/observer/observer_manager.rs b/src/frontend/src/observer/observer_manager.rs index ddf6ca489bf0c..38e84d213bf49 100644 --- a/src/frontend/src/observer/observer_manager.rs +++ b/src/frontend/src/observer/observer_manager.rs @@ -19,7 +19,7 @@ use itertools::Itertools; use parking_lot::RwLock; use risingwave_batch::worker_manager::worker_node_manager::WorkerNodeManagerRef; use risingwave_common::catalog::CatalogVersion; -use risingwave_common::hash::ParallelUnitMapping; +use risingwave_common::hash::WorkerSlotMapping; use risingwave_common::session_config::SessionConfig; use risingwave_common::system_param::local_manager::LocalSystemParamsManagerRef; use risingwave_common_service::observer_manager::{ObserverState, SubscribeFrontend}; @@ -27,12 +27,12 @@ use risingwave_pb::common::WorkerNode; use risingwave_pb::hummock::HummockVersionStats; use risingwave_pb::meta::relation::RelationInfo; use risingwave_pb::meta::subscribe_response::{Info, Operation}; -use risingwave_pb::meta::{FragmentParallelUnitMapping, MetaSnapshot, SubscribeResponse}; +use risingwave_pb::meta::{FragmentWorkerSlotMapping, MetaSnapshot, SubscribeResponse}; use risingwave_rpc_client::ComputeClientPoolRef; use tokio::sync::watch::Sender; use crate::catalog::root_catalog::Catalog; -use crate::catalog::FragmentId; +use crate::catalog::{FragmentId, SecretId}; use crate::scheduler::HummockSnapshotManagerRef; use crate::user::user_manager::UserInfoManager; use crate::user::UserInfoVersion; @@ -63,6 +63,7 @@ impl ObserverState for FrontendObserverNode { | Info::Schema(_) | Info::RelationGroup(_) | Info::Function(_) + | Info::Secret(_) | Info::Connection(_) => { self.handle_catalog_notification(resp); } @@ -72,7 +73,6 @@ impl ObserverState for FrontendObserverNode { Info::User(_) => { self.handle_user_notification(resp); } - Info::ParallelUnitMapping(_) => self.handle_fragment_mapping_notification(resp), Info::Snapshot(_) => { panic!( "receiving a snapshot in the middle is unsupported now {:?}", @@ -103,8 +103,9 @@ impl ObserverState for FrontendObserverNode { Info::HummockStats(stats) => { self.handle_table_stats_notification(stats); } - Info::ServingParallelUnitMappings(m) => { - self.handle_fragment_serving_mapping_notification(m.mappings, resp.operation()); + Info::StreamingWorkerSlotMapping(_) => self.handle_fragment_mapping_notification(resp), + Info::ServingWorkerSlotMappings(m) => { + self.handle_fragment_serving_mapping_notification(m.mappings, resp.operation()) } Info::Recovery(_) => { self.compute_client_pool.invalidate_all(); @@ -133,15 +134,16 @@ impl ObserverState for FrontendObserverNode { functions, connections, users, - parallel_unit_mappings, - serving_parallel_unit_mappings, nodes, hummock_snapshot, hummock_version: _, meta_backup_manifest_id: _, hummock_write_limits: _, + streaming_worker_slot_mappings, + serving_worker_slot_mappings, session_params, version, + secrets, } = snapshot; for db in databases { @@ -174,13 +176,17 @@ impl ObserverState for FrontendObserverNode { for connection in connections { catalog_guard.create_connection(&connection) } + for secret in secrets { + catalog_guard.create_secret(&secret) + } for user in users { user_guard.create_user(user) } + self.worker_node_manager.refresh( nodes, - convert_pu_mapping(¶llel_unit_mappings), - convert_pu_mapping(&serving_parallel_unit_mappings), + convert_worker_slot_mapping(&streaming_worker_slot_mappings), + convert_worker_slot_mapping(&serving_worker_slot_mappings), ); self.hummock_snapshot_manager .update(hummock_snapshot.unwrap()); @@ -263,7 +269,7 @@ impl FrontendObserverNode { ), Operation::Update => { let old_fragment_id = catalog_guard - .get_table_by_id(&table.id.into()) + .get_any_table_by_id(&table.id.into()) .unwrap() .fragment_id; catalog_guard.update_table(table); @@ -345,6 +351,16 @@ impl FrontendObserverNode { Operation::Update => catalog_guard.update_connection(connection), _ => panic!("receive an unsupported notify {:?}", resp), }, + Info::Secret(secret) => match resp.operation() { + Operation::Add => catalog_guard.create_secret(secret), + Operation::Delete => catalog_guard.drop_secret( + secret.database_id, + secret.schema_id, + SecretId::new(secret.id), + ), + Operation::Update => catalog_guard.update_secret(secret), + _ => panic!("receive an unsupported notify {:?}", resp), + }, _ => unreachable!(), } assert!( @@ -387,11 +403,11 @@ impl FrontendObserverNode { return; }; match info { - Info::ParallelUnitMapping(parallel_unit_mapping) => { - let fragment_id = parallel_unit_mapping.fragment_id; + Info::StreamingWorkerSlotMapping(streaming_worker_slot_mapping) => { + let fragment_id = streaming_worker_slot_mapping.fragment_id; let mapping = || { - ParallelUnitMapping::from_protobuf( - parallel_unit_mapping.mapping.as_ref().unwrap(), + WorkerSlotMapping::from_protobuf( + streaming_worker_slot_mapping.mapping.as_ref().unwrap(), ) }; @@ -417,20 +433,20 @@ impl FrontendObserverNode { fn handle_fragment_serving_mapping_notification( &mut self, - mappings: Vec, + mappings: Vec, op: Operation, ) { match op { Operation::Add | Operation::Update => { self.worker_node_manager - .upsert_serving_fragment_mapping(convert_pu_mapping(&mappings)); + .upsert_serving_fragment_mapping(convert_worker_slot_mapping(&mappings)); } Operation::Delete => self.worker_node_manager.remove_serving_fragment_mapping( &mappings.into_iter().map(|m| m.fragment_id).collect_vec(), ), Operation::Snapshot => { self.worker_node_manager - .set_serving_fragment_mapping(convert_pu_mapping(&mappings)); + .set_serving_fragment_mapping(convert_worker_slot_mapping(&mappings)); } _ => panic!("receive an unsupported notify {:?}", op), } @@ -470,17 +486,17 @@ impl FrontendObserverNode { } } -fn convert_pu_mapping( - parallel_unit_mappings: &[FragmentParallelUnitMapping], -) -> HashMap { - parallel_unit_mappings +fn convert_worker_slot_mapping( + worker_slot_mappings: &[FragmentWorkerSlotMapping], +) -> HashMap { + worker_slot_mappings .iter() .map( - |FragmentParallelUnitMapping { + |FragmentWorkerSlotMapping { fragment_id, mapping, }| { - let mapping = ParallelUnitMapping::from_protobuf(mapping.as_ref().unwrap()); + let mapping = WorkerSlotMapping::from_protobuf(mapping.as_ref().unwrap()); (*fragment_id, mapping) }, ) diff --git a/src/frontend/src/optimizer/logical_optimization.rs b/src/frontend/src/optimizer/logical_optimization.rs index 14fa66be68d5f..931a645b3d680 100644 --- a/src/frontend/src/optimizer/logical_optimization.rs +++ b/src/frontend/src/optimizer/logical_optimization.rs @@ -118,6 +118,14 @@ static DAG_TO_TREE: LazyLock = LazyLock::new(|| { ) }); +static STREAM_GENERATE_SERIES_WITH_NOW: LazyLock = LazyLock::new(|| { + OptimizationStage::new( + "Convert GENERATE_SERIES Ends With NOW", + vec![GenerateSeriesWithNowRule::create()], + ApplyOrder::TopDown, + ) +}); + static TABLE_FUNCTION_TO_PROJECT_SET: LazyLock = LazyLock::new(|| { OptimizationStage::new( "Table Function To Project Set", @@ -172,7 +180,7 @@ static GENERAL_UNNESTING_TRANS_APPLY_WITH_SHARE: LazyLock = // can't handle a join with `output_indices`. ProjectJoinSeparateRule::create(), ], - ApplyOrder::BottomUp, + ApplyOrder::TopDown, ) }); @@ -186,7 +194,7 @@ static GENERAL_UNNESTING_TRANS_APPLY_WITHOUT_SHARE: LazyLock // can't handle a join with `output_indices`. ProjectJoinSeparateRule::create(), ], - ApplyOrder::BottomUp, + ApplyOrder::TopDown, ) }); @@ -572,6 +580,9 @@ impl LogicalOptimizer { } plan = plan.optimize_by_rules(&SET_OPERATION_MERGE); plan = plan.optimize_by_rules(&SET_OPERATION_TO_JOIN); + // Convert `generate_series` ends with `now()` to a `Now` source. Only for streaming mode. + // Should be applied before converting table function to project set. + plan = plan.optimize_by_rules(&STREAM_GENERATE_SERIES_WITH_NOW); // In order to unnest a table function, we need to convert it into a `project_set` first. plan = plan.optimize_by_rules(&TABLE_FUNCTION_TO_PROJECT_SET); @@ -732,8 +743,8 @@ impl LogicalOptimizer { // Do a final column pruning and predicate pushing down to clean up the plan. plan = Self::column_pruning(plan, explain_trace, &ctx); if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() { - #[allow(unused_assignments)] - last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied(); + (#[allow(unused_assignments)] + last_total_rule_applied_before_predicate_pushdown) = ctx.total_rule_applied(); plan = Self::predicate_pushdown(plan, explain_trace, &ctx); } diff --git a/src/frontend/src/optimizer/mod.rs b/src/frontend/src/optimizer/mod.rs index e0b69d27f647c..c336254910826 100644 --- a/src/frontend/src/optimizer/mod.rs +++ b/src/frontend/src/optimizer/mod.rs @@ -40,7 +40,7 @@ mod plan_expr_visitor; mod rule; use std::assert_matches::assert_matches; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use fixedbitset::FixedBitSet; use itertools::Itertools as _; @@ -50,7 +50,7 @@ use plan_expr_rewriter::ConstEvalRewriter; use property::Order; use risingwave_common::bail; use risingwave_common::catalog::{ - ColumnCatalog, ColumnDesc, ColumnId, ConflictBehavior, Field, Schema, TableId, UserId, + ColumnCatalog, ColumnDesc, ColumnId, ConflictBehavior, Field, Schema, TableId, }; use risingwave_common::types::DataType; use risingwave_common::util::column_index_mapping::ColIndexMapping; @@ -64,7 +64,7 @@ use self::plan_node::generic::{self, PhysicalPlanRef}; use self::plan_node::{ stream_enforce_eowc_requirement, BatchProject, Convention, LogicalProject, LogicalSource, PartitionComputeInfo, StreamDml, StreamMaterialize, StreamProject, StreamRowIdGen, StreamSink, - StreamSubscription, StreamWatermarkFilter, ToStreamContext, + StreamWatermarkFilter, ToStreamContext, }; #[cfg(debug_assertions)] use self::plan_visitor::InputRefValidator; @@ -96,20 +96,74 @@ use crate::WithOptions; /// column in the result. #[derive(Debug, Clone)] pub struct PlanRoot { + // The current plan node. plan: PlanRef, + // The phase of the plan. + phase: PlanPhase, required_dist: RequiredDist, required_order: Order, out_fields: FixedBitSet, out_names: Vec, } +/// `PlanPhase` is used to track the phase of the `PlanRoot`. +/// Usually, it begins from `Logical` and ends with `Batch` or `Stream`, unless we want to construct a `PlanRoot` from an intermediate phase. +/// Typical phase transformation are: +/// - `Logical` -> `OptimizedLogicalForBatch` -> `Batch` +/// - `Logical` -> `OptimizedLogicalForStream` -> `Stream` +#[derive(Debug, Clone, PartialEq)] +pub enum PlanPhase { + Logical, + OptimizedLogicalForBatch, + OptimizedLogicalForStream, + Batch, + Stream, +} + impl PlanRoot { - pub fn new( + pub fn new_with_logical_plan( + plan: PlanRef, + required_dist: RequiredDist, + required_order: Order, + out_fields: FixedBitSet, + out_names: Vec, + ) -> Self { + assert_eq!(plan.convention(), Convention::Logical); + Self::new_inner( + plan, + PlanPhase::Logical, + required_dist, + required_order, + out_fields, + out_names, + ) + } + + pub fn new_with_batch_plan( plan: PlanRef, required_dist: RequiredDist, required_order: Order, out_fields: FixedBitSet, out_names: Vec, + ) -> Self { + assert_eq!(plan.convention(), Convention::Batch); + Self::new_inner( + plan, + PlanPhase::Batch, + required_dist, + required_order, + out_fields, + out_names, + ) + } + + fn new_inner( + plan: PlanRef, + phase: PlanPhase, + required_dist: RequiredDist, + required_order: Order, + out_fields: FixedBitSet, + out_names: Vec, ) -> Self { let input_schema = plan.schema(); assert_eq!(input_schema.fields().len(), out_fields.len()); @@ -117,6 +171,7 @@ impl PlanRoot { Self { plan, + phase, required_dist, required_order, out_fields, @@ -160,6 +215,8 @@ impl PlanRoot { /// example as insert source or subquery. This ignores Order but retains post-Order pruning /// (`out_fields`). pub fn into_unordered_subplan(self) -> PlanRef { + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); if self.out_fields.count_ones(..) == self.out_fields.len() { return self.plan; } @@ -178,6 +235,8 @@ impl PlanRoot { use crate::expr::{ExprImpl, ExprType, FunctionCall, InputRef}; use crate::utils::{Condition, IndexSet}; + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let Ok(select_idx) = self.out_fields.ones().exactly_one() else { bail!("subquery must return only one column"); }; @@ -192,6 +251,7 @@ impl PlanRoot { order_by: self.required_order.column_orders, filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }], IndexSet::empty(), self.plan, @@ -211,19 +271,38 @@ impl PlanRoot { } /// Apply logical optimization to the plan for stream. - pub fn gen_optimized_logical_plan_for_stream(&self) -> Result { - LogicalOptimizer::gen_optimized_logical_plan_for_stream(self.plan.clone()) + pub fn gen_optimized_logical_plan_for_stream(&mut self) -> Result { + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); + self.plan = LogicalOptimizer::gen_optimized_logical_plan_for_stream(self.plan.clone())?; + self.phase = PlanPhase::OptimizedLogicalForStream; + assert_eq!(self.plan.convention(), Convention::Logical); + Ok(self.plan.clone()) } /// Apply logical optimization to the plan for batch. - pub fn gen_optimized_logical_plan_for_batch(&self) -> Result { - LogicalOptimizer::gen_optimized_logical_plan_for_batch(self.plan.clone()) + pub fn gen_optimized_logical_plan_for_batch(&mut self) -> Result { + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); + self.plan = LogicalOptimizer::gen_optimized_logical_plan_for_batch(self.plan.clone())?; + self.phase = PlanPhase::OptimizedLogicalForBatch; + assert_eq!(self.plan.convention(), Convention::Logical); + Ok(self.plan.clone()) } /// Optimize and generate a singleton batch physical plan without exchange nodes. pub fn gen_batch_plan(&mut self) -> Result { - // Logical optimization - let mut plan = self.gen_optimized_logical_plan_for_batch()?; + assert_eq!(self.plan.convention(), Convention::Logical); + let mut plan = match self.phase { + PlanPhase::Logical => { + // Logical optimization + self.gen_optimized_logical_plan_for_batch()? + } + PlanPhase::OptimizedLogicalForBatch => self.plan.clone(), + PlanPhase::Batch | PlanPhase::OptimizedLogicalForStream | PlanPhase::Stream => { + panic!("unexpected phase") + } + }; if TemporalJoinValidator::exist_dangling_temporal_scan(plan.clone()) { return Err(ErrorCode::NotSupported( @@ -285,13 +364,18 @@ impl PlanRoot { ctx.trace(plan.explain_to_string()); } - Ok(plan) + self.plan = plan; + self.phase = PlanPhase::Batch; + assert_eq!(self.plan.convention(), Convention::Batch); + Ok(self.plan.clone()) } /// Optimize and generate a batch query plan for distributed execution. - pub fn gen_batch_distributed_plan(&mut self, batch_plan: PlanRef) -> Result { - self.set_required_dist(RequiredDist::single()); - let mut plan = batch_plan; + pub fn gen_batch_distributed_plan(mut self) -> Result { + assert_eq!(self.phase, PlanPhase::Batch); + assert_eq!(self.plan.convention(), Convention::Batch); + self.required_dist = RequiredDist::single(); + let mut plan = self.plan; // Convert to distributed plan plan = plan.to_distributed_with_required(&self.required_order, &self.required_dist)?; @@ -319,12 +403,15 @@ impl PlanRoot { ApplyOrder::BottomUp, )); + assert_eq!(plan.convention(), Convention::Batch); Ok(plan) } /// Optimize and generate a batch query plan for local execution. - pub fn gen_batch_local_plan(&mut self, batch_plan: PlanRef) -> Result { - let mut plan = batch_plan; + pub fn gen_batch_local_plan(self) -> Result { + assert_eq!(self.phase, PlanPhase::Batch); + assert_eq!(self.plan.convention(), Convention::Batch); + let mut plan = self.plan; // Convert to local plan node plan = plan.to_local_with_order_required(&self.required_order)?; @@ -359,11 +446,14 @@ impl PlanRoot { ApplyOrder::BottomUp, )); + assert_eq!(plan.convention(), Convention::Batch); Ok(plan) } /// Generate optimized stream plan fn gen_optimized_stream_plan(&mut self, emit_on_window_close: bool) -> Result { + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let stream_scan_type = if self.should_use_arrangement_backfill() { StreamScanType::ArrangementBackfill } else { @@ -377,6 +467,8 @@ impl PlanRoot { emit_on_window_close: bool, stream_scan_type: StreamScanType, ) -> Result { + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let ctx = self.plan.ctx(); let _explain_trace = ctx.is_explain_trace(); @@ -424,7 +516,10 @@ impl PlanRoot { ).into()); } - Ok(plan) + self.plan = plan; + self.phase = PlanPhase::Stream; + assert_eq!(self.plan.convention(), Convention::Stream); + Ok(self.plan.clone()) } /// Generate create index or create materialize view plan. @@ -433,6 +528,8 @@ impl PlanRoot { emit_on_window_close: bool, stream_scan_type: StreamScanType, ) -> Result { + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let ctx = self.plan.ctx(); let explain_trace = ctx.is_explain_trace(); @@ -451,7 +548,6 @@ impl PlanRoot { ).into()); } let plan = self.gen_optimized_logical_plan_for_stream()?; - let (plan, out_col_change) = { let (plan, out_col_change) = plan.logical_rewrite_for_stream(&mut Default::default())?; @@ -523,7 +619,7 @@ impl PlanRoot { /// Optimize and generate a create table plan. #[allow(clippy::too_many_arguments)] pub fn gen_table_plan( - &mut self, + mut self, context: OptimizerContextRef, table_name: String, columns: Vec, @@ -538,7 +634,11 @@ impl PlanRoot { with_external_source: bool, retention_seconds: Option, ) -> Result { + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let stream_plan = self.gen_optimized_stream_plan(false)?; + assert_eq!(self.phase, PlanPhase::Stream); + assert_eq!(stream_plan.convention(), Convention::Stream); assert!(!pk_column_ids.is_empty() || row_id_index.is_some()); @@ -767,14 +867,17 @@ impl PlanRoot { /// Optimize and generate a create materialized view plan. pub fn gen_materialize_plan( - &mut self, + mut self, mv_name: String, definition: String, emit_on_window_close: bool, ) -> Result { let cardinality = self.compute_cardinality(); + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let stream_plan = self.gen_optimized_stream_plan(emit_on_window_close)?; - + assert_eq!(self.phase, PlanPhase::Stream); + assert_eq!(stream_plan.convention(), Convention::Stream); StreamMaterialize::create( stream_plan, mv_name, @@ -791,13 +894,17 @@ impl PlanRoot { /// Optimize and generate a create index plan. pub fn gen_index_plan( - &mut self, + mut self, index_name: String, definition: String, retention_seconds: Option, ) -> Result { let cardinality = self.compute_cardinality(); + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let stream_plan = self.gen_optimized_stream_plan(false)?; + assert_eq!(self.phase, PlanPhase::Stream); + assert_eq!(stream_plan.convention(), Convention::Stream); StreamMaterialize::create( stream_plan, @@ -835,9 +942,12 @@ impl PlanRoot { } else { StreamScanType::Backfill }; + assert_eq!(self.phase, PlanPhase::Logical); + assert_eq!(self.plan.convention(), Convention::Logical); let stream_plan = self.gen_optimized_stream_plan_inner(emit_on_window_close, stream_scan_type)?; - + assert_eq!(self.phase, PlanPhase::Stream); + assert_eq!(stream_plan.convention(), Convention::Stream); StreamSink::create( stream_plan, sink_name, @@ -855,46 +965,6 @@ impl PlanRoot { ) } - #[allow(clippy::too_many_arguments)] - /// Optimize and generate a create subscription plan. - pub fn gen_subscription_plan( - &mut self, - database_id: u32, - schema_id: u32, - dependent_relations: HashSet, - subscription_name: String, - definition: String, - properties: WithOptions, - emit_on_window_close: bool, - subscription_from_table_name: String, - user_id: UserId, - ) -> Result { - let stream_scan_type = StreamScanType::UpstreamOnly; - let stream_plan = - self.gen_optimized_stream_plan_inner(emit_on_window_close, stream_scan_type)?; - - StreamSubscription::create( - database_id, - schema_id, - dependent_relations, - stream_plan, - subscription_name, - subscription_from_table_name, - self.required_dist.clone(), - self.required_order.clone(), - self.out_fields.clone(), - self.out_names.clone(), - definition, - properties, - user_id, - ) - } - - /// Set the plan root's required dist. - pub fn set_required_dist(&mut self, required_dist: RequiredDist) { - self.required_dist = required_dist; - } - pub fn should_use_arrangement_backfill(&self) -> bool { let ctx = self.plan.ctx(); let session_ctx = ctx.session_ctx(); @@ -972,6 +1042,10 @@ fn require_additional_exchange_on_root_in_distributed_mode(plan: PlanRef) -> boo plan.node_type() == PlanNodeType::BatchSeqScan } + fn is_log_table(plan: &PlanRef) -> bool { + plan.node_type() == PlanNodeType::BatchLogSeqScan + } + fn is_source(plan: &PlanRef) -> bool { plan.node_type() == PlanNodeType::BatchSource || plan.node_type() == PlanNodeType::BatchKafkaScan @@ -996,6 +1070,7 @@ fn require_additional_exchange_on_root_in_distributed_mode(plan: PlanRef) -> boo || exist_and_no_exchange_before(&plan, is_insert) || exist_and_no_exchange_before(&plan, is_update) || exist_and_no_exchange_before(&plan, is_delete) + || exist_and_no_exchange_before(&plan, is_log_table) } /// The purpose is same as `require_additional_exchange_on_root_in_distributed_mode`. We separate @@ -1023,11 +1098,7 @@ fn require_additional_exchange_on_root_in_local_mode(plan: PlanRef) -> bool { #[cfg(test)] mod tests { - use risingwave_common::catalog::Field; - use risingwave_common::types::DataType; - use super::*; - use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; #[tokio::test] @@ -1044,7 +1115,7 @@ mod tests { .into(); let out_fields = FixedBitSet::with_capacity_and_blocks(2, [1]); let out_names = vec!["v1".into()]; - let root = PlanRoot::new( + let root = PlanRoot::new_with_logical_plan( values, RequiredDist::Any, Order::any(), diff --git a/src/frontend/src/optimizer/optimizer_context.rs b/src/frontend/src/optimizer/optimizer_context.rs index eb6e410378749..35c5a3ee9129b 100644 --- a/src/frontend/src/optimizer/optimizer_context.rs +++ b/src/frontend/src/optimizer/optimizer_context.rs @@ -12,20 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::convert::Into; use core::fmt::Formatter; use std::cell::{RefCell, RefMut}; +use std::collections::HashMap; use std::marker::PhantomData; use std::rc::Rc; use std::sync::Arc; use risingwave_sqlparser::ast::{ExplainOptions, ExplainType}; +use crate::binder::ShareId; use crate::expr::{CorrelatedId, SessionTimezone}; use crate::handler::HandlerArgs; use crate::optimizer::plan_node::PlanNodeId; use crate::session::SessionImpl; use crate::utils::{OverwriteOptions, WithOptions}; +use crate::PlanRef; const RESERVED_ID_NUM: u16 = 10000; @@ -58,6 +60,9 @@ pub struct OptimizerContext { /// Store the configs can be overwritten in with clause /// if not specified, use the value from session variable. overwrite_options: OverwriteOptions, + /// Store the mapping between `share_id` and the corresponding + /// `PlanRef`, used by rcte's planning. (e.g., in `LogicalCteRef`) + rcte_cache: RefCell>, _phantom: PhantomUnsend, } @@ -91,6 +96,7 @@ impl OptimizerContext { next_expr_display_id: RefCell::new(RESERVED_ID_NUM.into()), total_rule_applied: RefCell::new(0), overwrite_options, + rcte_cache: RefCell::new(HashMap::new()), _phantom: Default::default(), } } @@ -113,6 +119,7 @@ impl OptimizerContext { next_expr_display_id: RefCell::new(0), total_rule_applied: RefCell::new(0), overwrite_options: OverwriteOptions::default(), + rcte_cache: RefCell::new(HashMap::new()), _phantom: Default::default(), } .into() @@ -230,6 +237,14 @@ impl OptimizerContext { pub fn get_session_timezone(&self) -> String { self.session_timezone.borrow().timezone() } + + pub fn get_rcte_cache_plan(&self, id: &ShareId) -> Option { + self.rcte_cache.borrow().get(id).cloned() + } + + pub fn insert_rcte_cache_plan(&self, id: ShareId, plan: PlanRef) { + self.rcte_cache.borrow_mut().insert(id, plan); + } } impl std::fmt::Debug for OptimizerContext { diff --git a/src/frontend/src/optimizer/plan_expr_rewriter/const_eval_rewriter.rs b/src/frontend/src/optimizer/plan_expr_rewriter/const_eval_rewriter.rs index 43b76891f3566..0844bdb33a85b 100644 --- a/src/frontend/src/optimizer/plan_expr_rewriter/const_eval_rewriter.rs +++ b/src/frontend/src/optimizer/plan_expr_rewriter/const_eval_rewriter.rs @@ -13,7 +13,7 @@ // limitations under the License. use crate::error::RwError; -use crate::expr::{Expr, ExprImpl, ExprRewriter, Literal}; +use crate::expr::{default_rewrite_expr, Expr, ExprImpl, ExprRewriter, Literal}; pub(crate) struct ConstEvalRewriter { pub(crate) error: Option, @@ -31,21 +31,10 @@ impl ExprRewriter for ConstEvalRewriter { expr } } + } else if let ExprImpl::Parameter(_) = expr { + unreachable!("Parameter should not appear here. It will be replaced by a literal before this step.") } else { - match expr { - ExprImpl::InputRef(inner) => self.rewrite_input_ref(*inner), - ExprImpl::Literal(inner) => self.rewrite_literal(*inner), - ExprImpl::FunctionCall(inner) => self.rewrite_function_call(*inner), - ExprImpl::FunctionCallWithLambda(inner) => self.rewrite_function_call_with_lambda(*inner), - ExprImpl::AggCall(inner) => self.rewrite_agg_call(*inner), - ExprImpl::Subquery(inner) => self.rewrite_subquery(*inner), - ExprImpl::CorrelatedInputRef(inner) => self.rewrite_correlated_input_ref(*inner), - ExprImpl::TableFunction(inner) => self.rewrite_table_function(*inner), - ExprImpl::WindowFunction(inner) => self.rewrite_window_function(*inner), - ExprImpl::UserDefinedFunction(inner) => self.rewrite_user_defined_function(*inner), - ExprImpl::Parameter(_) => unreachable!("Parameter should not appear here. It will be replaced by a literal before this step."), - ExprImpl::Now(inner) => self.rewrite_now(*inner), - } + default_rewrite_expr(self, expr) } } } diff --git a/src/frontend/src/optimizer/plan_expr_visitor/expr_counter.rs b/src/frontend/src/optimizer/plan_expr_visitor/expr_counter.rs index 68ce5b93b0441..b636218338c2d 100644 --- a/src/frontend/src/optimizer/plan_expr_visitor/expr_counter.rs +++ b/src/frontend/src/optimizer/plan_expr_visitor/expr_counter.rs @@ -14,7 +14,7 @@ use std::collections::HashMap; -use crate::expr::{ExprImpl, ExprType, ExprVisitor, FunctionCall}; +use crate::expr::{default_visit_expr, ExprImpl, ExprType, ExprVisitor, FunctionCall}; /// `ExprCounter` is used by `CseRewriter`. #[derive(Default)] @@ -35,20 +35,7 @@ impl ExprVisitor for CseExprCounter { return; } - match expr { - ExprImpl::InputRef(inner) => self.visit_input_ref(inner), - ExprImpl::Literal(inner) => self.visit_literal(inner), - ExprImpl::FunctionCall(inner) => self.visit_function_call(inner), - ExprImpl::FunctionCallWithLambda(inner) => self.visit_function_call_with_lambda(inner), - ExprImpl::AggCall(inner) => self.visit_agg_call(inner), - ExprImpl::Subquery(inner) => self.visit_subquery(inner), - ExprImpl::CorrelatedInputRef(inner) => self.visit_correlated_input_ref(inner), - ExprImpl::TableFunction(inner) => self.visit_table_function(inner), - ExprImpl::WindowFunction(inner) => self.visit_window_function(inner), - ExprImpl::UserDefinedFunction(inner) => self.visit_user_defined_function(inner), - ExprImpl::Parameter(inner) => self.visit_parameter(inner), - ExprImpl::Now(inner) => self.visit_now(inner), - } + default_visit_expr(self, expr); } fn visit_function_call(&mut self, func_call: &FunctionCall) { diff --git a/src/frontend/src/optimizer/plan_expr_visitor/strong.rs b/src/frontend/src/optimizer/plan_expr_visitor/strong.rs index e30cdb0b6e314..0cf1cd0a07a35 100644 --- a/src/frontend/src/optimizer/plan_expr_visitor/strong.rs +++ b/src/frontend/src/optimizer/plan_expr_visitor/strong.rs @@ -81,6 +81,7 @@ impl Strong { | ExprType::IsDistinctFrom | ExprType::IsNotDistinctFrom | ExprType::IsTrue + | ExprType::QuoteNullable | ExprType::IsNotTrue | ExprType::IsFalse | ExprType::IsNotFalse => false, @@ -180,6 +181,7 @@ impl Strong { | ExprType::ToAscii | ExprType::ToHex | ExprType::QuoteIdent + | ExprType::QuoteLiteral | ExprType::Sin | ExprType::Cos | ExprType::Tan @@ -287,7 +289,9 @@ impl Strong { | ExprType::JsonbPathQueryFirst | ExprType::JsonbPopulateRecord | ExprType::JsonbToRecord + | ExprType::JsonbSet | ExprType::Vnode + | ExprType::TestPaidTier | ExprType::Proctime | ExprType::PgSleep | ExprType::PgSleepFor @@ -300,7 +304,11 @@ impl Strong { | ExprType::PgIndexesSize | ExprType::PgRelationSize | ExprType::PgGetSerialSequence + | ExprType::PgIndexColumnHasProperty | ExprType::IcebergTransform + | ExprType::HasTablePrivilege + | ExprType::HasAnyColumnPrivilege + | ExprType::HasSchemaPrivilege | ExprType::InetAton | ExprType::InetNtoa => false, ExprType::Unspecified => unreachable!(), diff --git a/src/frontend/src/optimizer/plan_node/batch_delete.rs b/src/frontend/src/optimizer/plan_node/batch_delete.rs index 62499a495d1de..8980f58a9e2c4 100644 --- a/src/frontend/src/optimizer/plan_node/batch_delete.rs +++ b/src/frontend/src/optimizer/plan_node/batch_delete.rs @@ -22,7 +22,6 @@ use super::{ }; use crate::error::Result; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::PhysicalPlanRef; use crate::optimizer::plan_node::{utils, ToLocalBatch}; use crate::optimizer::plan_visitor::DistributedDmlVisitor; use crate::optimizer::property::{Distribution, Order, RequiredDist}; diff --git a/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs b/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs index af5f4c4accbb6..341d1281009eb 100644 --- a/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs +++ b/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs @@ -17,7 +17,7 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::HashAggNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::utils::impl_distill_by_unit; use super::{ ExprRewritable, PlanBase, PlanNodeType, PlanRef, PlanTreeNodeUnary, ToBatchPb, diff --git a/src/frontend/src/optimizer/plan_node/batch_hash_join.rs b/src/frontend/src/optimizer/plan_node/batch_hash_join.rs index 8b92f722490d5..399817336e4ca 100644 --- a/src/frontend/src/optimizer/plan_node/batch_hash_join.rs +++ b/src/frontend/src/optimizer/plan_node/batch_hash_join.rs @@ -18,10 +18,9 @@ use risingwave_pb::batch_plan::HashJoinNode; use risingwave_pb::plan_common::JoinType; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef}; use super::utils::{childless_record, Distill}; use super::{ - EqJoinPredicate, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, + generic, EqJoinPredicate, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, ToDistributedBatch, }; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_iceberg_scan.rs b/src/frontend/src/optimizer/plan_node/batch_iceberg_scan.rs index 699d32c5a2bf5..0ac2451759543 100644 --- a/src/frontend/src/optimizer/plan_node/batch_iceberg_scan.rs +++ b/src/frontend/src/optimizer/plan_node/batch_iceberg_scan.rs @@ -109,6 +109,7 @@ impl ToBatchPb for BatchIcebergScan { .collect(), with_properties: source_catalog.with_properties.clone().into_iter().collect(), split: vec![], + secret_refs: Default::default(), }) } } diff --git a/src/frontend/src/optimizer/plan_node/batch_insert.rs b/src/frontend/src/optimizer/plan_node/batch_insert.rs index 1dbe387bb708f..8a3e511efc83c 100644 --- a/src/frontend/src/optimizer/plan_node/batch_insert.rs +++ b/src/frontend/src/optimizer/plan_node/batch_insert.rs @@ -18,7 +18,6 @@ use risingwave_pb::batch_plan::InsertNode; use risingwave_pb::plan_common::{DefaultColumns, IndexAndExpr}; use super::batch::prelude::*; -use super::generic::GenericPlanRef; use super::utils::{childless_record, Distill}; use super::{generic, ExprRewritable, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_kafka_scan.rs b/src/frontend/src/optimizer/plan_node/batch_kafka_scan.rs index 423fe2a6771ee..8dca305121399 100644 --- a/src/frontend/src/optimizer/plan_node/batch_kafka_scan.rs +++ b/src/frontend/src/optimizer/plan_node/batch_kafka_scan.rs @@ -131,6 +131,7 @@ impl ToBatchPb for BatchKafkaScan { .collect(), with_properties: source_catalog.with_properties.clone().into_iter().collect(), split: vec![], + secret_refs: Default::default(), }) } } diff --git a/src/frontend/src/optimizer/plan_node/batch_limit.rs b/src/frontend/src/optimizer/plan_node/batch_limit.rs index 1dfe9215be1ea..ef717d179130e 100644 --- a/src/frontend/src/optimizer/plan_node/batch_limit.rs +++ b/src/frontend/src/optimizer/plan_node/batch_limit.rs @@ -16,7 +16,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::LimitNode; use super::batch::prelude::*; -use super::generic::PhysicalPlanRef; use super::utils::impl_distill_by_unit; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, diff --git a/src/frontend/src/optimizer/plan_node/batch_log_seq_scan.rs b/src/frontend/src/optimizer/plan_node/batch_log_seq_scan.rs new file mode 100644 index 0000000000000..93132ce65e51c --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/batch_log_seq_scan.rs @@ -0,0 +1,142 @@ +// Copyright 2024 RisingWave Labs +// +// 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 pretty_xmlish::{Pretty, XmlNode}; +use risingwave_pb::batch_plan::plan_node::NodeBody; +use risingwave_pb::batch_plan::LogRowSeqScanNode; +use risingwave_pb::common::BatchQueryEpoch; + +use super::batch::prelude::*; +use super::utils::{childless_record, Distill}; +use super::{generic, ExprRewritable, PlanBase, PlanRef, ToDistributedBatch, TryToBatchPb}; +use crate::catalog::ColumnId; +use crate::error::Result; +use crate::optimizer::plan_node::expr_visitable::ExprVisitable; +use crate::optimizer::plan_node::ToLocalBatch; +use crate::optimizer::property::{Distribution, DistributionDisplay, Order}; +use crate::scheduler::SchedulerResult; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct BatchLogSeqScan { + pub base: PlanBase, + core: generic::LogScan, +} + +impl BatchLogSeqScan { + fn new_inner(core: generic::LogScan, dist: Distribution) -> Self { + let order = Order::any(); + let base = PlanBase::new_batch(core.ctx(), core.schema(), dist, order); + + Self { base, core } + } + + pub fn new(core: generic::LogScan) -> Self { + // Use `Single` by default, will be updated later with `clone_with_dist`. + Self::new_inner(core, Distribution::Single) + } + + fn clone_with_dist(&self) -> Self { + Self::new_inner( + self.core.clone(), + match self.core.distribution_key() { + None => Distribution::SomeShard, + Some(distribution_key) => { + if distribution_key.is_empty() { + Distribution::Single + } else { + Distribution::UpstreamHashShard( + distribution_key, + self.core.table_desc.table_id, + ) + } + } + }, + ) + } + + /// Get a reference to the batch seq scan's logical. + #[must_use] + pub fn core(&self) -> &generic::LogScan { + &self.core + } +} + +impl_plan_tree_node_for_leaf! { BatchLogSeqScan } + +impl Distill for BatchLogSeqScan { + fn distill<'a>(&self) -> XmlNode<'a> { + let verbose = self.base.ctx().is_explain_verbose(); + let mut vec = Vec::with_capacity(3); + vec.push(("table", Pretty::from(self.core.table_name.clone()))); + vec.push(("columns", self.core.columns_pretty(verbose))); + + if verbose { + let dist = Pretty::display(&DistributionDisplay { + distribution: self.distribution(), + input_schema: self.base.schema(), + }); + vec.push(("distribution", dist)); + } + + childless_record("BatchScan", vec) + } +} + +impl ToDistributedBatch for BatchLogSeqScan { + fn to_distributed(&self) -> Result { + Ok(self.clone_with_dist().into()) + } +} + +impl TryToBatchPb for BatchLogSeqScan { + fn try_to_batch_prost_body(&self) -> SchedulerResult { + Ok(NodeBody::LogRowSeqScan(LogRowSeqScanNode { + table_desc: Some(self.core.table_desc.try_to_protobuf()?), + column_ids: self + .core + .output_column_ids() + .iter() + .map(ColumnId::get_id) + .collect(), + vnode_bitmap: None, + old_epoch: Some(BatchQueryEpoch { + epoch: Some(risingwave_pb::common::batch_query_epoch::Epoch::Committed( + self.core.old_epoch, + )), + }), + new_epoch: Some(BatchQueryEpoch { + epoch: Some(risingwave_pb::common::batch_query_epoch::Epoch::Committed( + self.core.new_epoch, + )), + }), + })) + } +} + +impl ToLocalBatch for BatchLogSeqScan { + fn to_local(&self) -> Result { + let dist = if let Some(distribution_key) = self.core.distribution_key() + && !distribution_key.is_empty() + { + Distribution::UpstreamHashShard(distribution_key, self.core.table_desc.table_id) + } else { + Distribution::SomeShard + }; + Ok(Self::new_inner(self.core.clone(), dist).into()) + } +} + +impl ExprRewritable for BatchLogSeqScan {} + +impl ExprVisitable for BatchLogSeqScan {} diff --git a/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs b/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs index a20b4ad316cd7..a181c590e0e65 100644 --- a/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs +++ b/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs @@ -18,9 +18,8 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::{DistributedLookupJoinNode, LocalLookupJoinNode}; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef}; use super::utils::{childless_record, Distill}; -use super::ExprRewritable; +use super::{generic, ExprRewritable}; use crate::error::Result; use crate::expr::{Expr, ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs b/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs index 94b8b3e6e0483..e35d6976672c4 100644 --- a/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs +++ b/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs @@ -17,7 +17,7 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::MaxOneRowNode; use super::batch::prelude::*; -use super::generic::{DistillUnit, PhysicalPlanRef}; +use super::generic::DistillUnit; use super::utils::Distill; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, diff --git a/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs b/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs index 401166b1a298a..5d07aad86017b 100644 --- a/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs +++ b/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs @@ -17,9 +17,10 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::NestedLoopJoinNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef}; use super::utils::{childless_record, Distill}; -use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, ToDistributedBatch}; +use super::{ + generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, ToDistributedBatch, +}; use crate::error::Result; use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/batch_over_window.rs b/src/frontend/src/optimizer/plan_node/batch_over_window.rs index 1712e87bbabbf..b9355fb117098 100644 --- a/src/frontend/src/optimizer/plan_node/batch_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/batch_over_window.rs @@ -17,7 +17,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::SortOverWindowNode; use super::batch::prelude::*; -use super::batch::BatchPlanRef; use super::generic::PlanWindowFunction; use super::utils::impl_distill_by_unit; use super::{ diff --git a/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs b/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs index a112e36c3a98f..ff20df7a4d177 100644 --- a/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs +++ b/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs @@ -16,7 +16,7 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::SortAggNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::utils::impl_distill_by_unit; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_sort.rs b/src/frontend/src/optimizer/plan_node/batch_sort.rs index 413103ee98c89..9136e3f7c685a 100644 --- a/src/frontend/src/optimizer/plan_node/batch_sort.rs +++ b/src/frontend/src/optimizer/plan_node/batch_sort.rs @@ -17,7 +17,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::SortNode; use super::batch::prelude::*; -use super::batch::BatchPlanRef; use super::utils::{childless_record, Distill}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs b/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs index 7393c10edc8f0..f8ea17dcd837e 100644 --- a/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs +++ b/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs @@ -17,7 +17,7 @@ use risingwave_pb::batch_plan::SortAggNode; use risingwave_pb::expr::ExprNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::utils::impl_distill_by_unit; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_source.rs b/src/frontend/src/optimizer/plan_node/batch_source.rs index 3836940be745e..ac5e25d703636 100644 --- a/src/frontend/src/optimizer/plan_node/batch_source.rs +++ b/src/frontend/src/optimizer/plan_node/batch_source.rs @@ -113,6 +113,7 @@ impl ToBatchPb for BatchSource { .collect(), with_properties: source_catalog.with_properties.clone().into_iter().collect(), split: vec![], + secret_refs: Default::default(), }) } } diff --git a/src/frontend/src/optimizer/plan_node/batch_topn.rs b/src/frontend/src/optimizer/plan_node/batch_topn.rs index d8f96668f88a7..643ef0fcc01fc 100644 --- a/src/frontend/src/optimizer/plan_node/batch_topn.rs +++ b/src/frontend/src/optimizer/plan_node/batch_topn.rs @@ -22,7 +22,6 @@ use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, }; use crate::error::Result; -use crate::optimizer::plan_node::batch::BatchPlanRef; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; use crate::optimizer::plan_node::{BatchLimit, ToLocalBatch}; use crate::optimizer::property::{Order, RequiredDist}; diff --git a/src/frontend/src/optimizer/plan_node/batch_update.rs b/src/frontend/src/optimizer/plan_node/batch_update.rs index 34d1eb3db3d74..d0351e6fdec2e 100644 --- a/src/frontend/src/optimizer/plan_node/batch_update.rs +++ b/src/frontend/src/optimizer/plan_node/batch_update.rs @@ -18,7 +18,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::UpdateNode; use super::batch::prelude::*; -use super::generic::GenericPlanRef; use super::utils::impl_distill_by_unit; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, diff --git a/src/frontend/src/optimizer/plan_node/convert.rs b/src/frontend/src/optimizer/plan_node/convert.rs index a39ed7d3b048d..6f98073304d8b 100644 --- a/src/frontend/src/optimizer/plan_node/convert.rs +++ b/src/frontend/src/optimizer/plan_node/convert.rs @@ -19,8 +19,7 @@ use risingwave_common::catalog::FieldDisplay; use risingwave_pb::stream_plan::StreamScanType; use super::*; -use crate::optimizer::property::{Order, RequiredDist}; -use crate::utils::ColIndexMapping; +use crate::optimizer::property::RequiredDist; use crate::{for_batch_plan_nodes, for_logical_plan_nodes, for_stream_plan_nodes}; /// `ToStream` converts a logical plan node to streaming physical node @@ -85,11 +84,6 @@ pub fn stream_enforce_eowc_requirement( } Ok(StreamEowcSort::new(plan, watermark_col_idx).into()) } - } else if !emit_on_window_close && plan.emit_on_window_close() { - Err(ErrorCode::InternalError( - "Some bad thing happened, the generated plan is not correct.".to_string(), - ) - .into()) } else { Ok(plan) } diff --git a/src/frontend/src/optimizer/plan_node/expr_rewritable.rs b/src/frontend/src/optimizer/plan_node/expr_rewritable.rs index 33deebde1cdf9..4a30a599742ed 100644 --- a/src/frontend/src/optimizer/plan_node/expr_rewritable.rs +++ b/src/frontend/src/optimizer/plan_node/expr_rewritable.rs @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::ops::Deref; - use super::*; -use crate::expr::ExprRewriter; /// Rewrites expressions in a `PlanRef`. Due to `Share` operator, /// the `ExprRewriter` needs to be idempotent i.e., applying it more than once diff --git a/src/frontend/src/optimizer/plan_node/expr_visitable.rs b/src/frontend/src/optimizer/plan_node/expr_visitable.rs index e4454fa92a7e5..e5527dfa17785 100644 --- a/src/frontend/src/optimizer/plan_node/expr_visitable.rs +++ b/src/frontend/src/optimizer/plan_node/expr_visitable.rs @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::ops::Deref; - use super::*; -use crate::expr::ExprVisitor; /// Vistis expressions in a `PlanRef`. /// To visit recursively, call `visit_exprs_recursive` on [`VisitExprsRecursive`]. diff --git a/src/frontend/src/optimizer/plan_node/generic/agg.rs b/src/frontend/src/optimizer/plan_node/generic/agg.rs index faff0c415f925..8f999988c4824 100644 --- a/src/frontend/src/optimizer/plan_node/generic/agg.rs +++ b/src/frontend/src/optimizer/plan_node/generic/agg.rs @@ -13,6 +13,7 @@ // limitations under the License. use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::sync::Arc; use std::{fmt, vec}; use fixedbitset::FixedBitSet; @@ -26,10 +27,11 @@ use risingwave_common::util::value_encoding::DatumToProtoExt; use risingwave_expr::aggregate::{agg_kinds, AggKind}; use risingwave_expr::sig::{FuncBuilder, FUNCTION_REGISTRY}; use risingwave_pb::expr::{PbAggCall, PbConstant}; -use risingwave_pb::stream_plan::{agg_call_state, AggCallState as AggCallStatePb}; +use risingwave_pb::stream_plan::{agg_call_state, AggCallState as PbAggCallState}; use super::super::utils::TableCatalogBuilder; use super::{impl_distill_unit_from_fields, stream, GenericPlanNode, GenericPlanRef}; +use crate::catalog::function_catalog::FunctionCatalog; use crate::expr::{Expr, ExprRewriter, ExprVisitor, InputRef, InputRefDisplay, Literal}; use crate::optimizer::optimizer_context::OptimizerContextRef; use crate::optimizer::plan_node::batch::BatchPlanRef; @@ -227,8 +229,8 @@ pub enum AggCallState { } impl AggCallState { - pub fn into_prost(self, state: &mut BuildFragmentGraphState) -> AggCallStatePb { - AggCallStatePb { + pub fn into_prost(self, state: &mut BuildFragmentGraphState) -> PbAggCallState { + PbAggCallState { inner: Some(match self { AggCallState::Value => { agg_call_state::Inner::ValueState(agg_call_state::ValueState {}) @@ -520,6 +522,11 @@ impl Agg { .iter() .zip_eq_fast(&mut out_fields[self.group_key.len()..]) { + if agg_call.agg_kind == AggKind::UserDefined { + // for user defined aggregate, the state type is always BYTEA + field.data_type = DataType::Bytea; + continue; + } let sig = FUNCTION_REGISTRY .get( agg_call.agg_kind, @@ -703,6 +710,9 @@ pub struct PlanAggCall { /// `filter` evaluates to `true` will be fed to the aggregate function. pub filter: Condition, pub direct_args: Vec, + + /// Catalog of user defined aggregate function. + pub user_defined: Option>, } impl fmt::Debug for PlanAggCall { @@ -771,6 +781,7 @@ impl PlanAggCall { r#type: Some(x.return_type().to_protobuf()), }) .collect(), + udf: self.user_defined.as_ref().map(|c| c.as_ref().into()), } } @@ -797,6 +808,7 @@ impl PlanAggCall { order_by: vec![], filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, } } diff --git a/src/frontend/src/optimizer/plan_node/generic/cdc_scan.rs b/src/frontend/src/optimizer/plan_node/generic/cdc_scan.rs index f742238de8912..2d7d708291e47 100644 --- a/src/frontend/src/optimizer/plan_node/generic/cdc_scan.rs +++ b/src/frontend/src/optimizer/plan_node/generic/cdc_scan.rs @@ -13,19 +13,28 @@ // limitations under the License. use std::rc::Rc; +use std::str::FromStr; +use anyhow::anyhow; use educe::Educe; use fixedbitset::FixedBitSet; use pretty_xmlish::Pretty; use risingwave_common::catalog::{CdcTableDesc, ColumnDesc, Field, Schema}; use risingwave_common::util::column_index_mapping::ColIndexMapping; use risingwave_common::util::sort_util::ColumnOrder; +use risingwave_connector::source::cdc::{ + CDC_BACKFILL_ENABLE_KEY, CDC_BACKFILL_SNAPSHOT_BATCH_SIZE_KEY, + CDC_BACKFILL_SNAPSHOT_INTERVAL_KEY, +}; +use risingwave_pb::stream_plan::StreamCdcScanOptions; use super::GenericPlanNode; use crate::catalog::ColumnId; +use crate::error::Result; use crate::expr::{ExprRewriter, ExprVisitor}; use crate::optimizer::optimizer_context::OptimizerContextRef; use crate::optimizer::property::FunctionalDependencySet; +use crate::WithOptions; /// [`CdcScan`] reads rows of a table from an external upstream database #[derive(Debug, Clone, Educe)] @@ -40,7 +49,59 @@ pub struct CdcScan { #[educe(Hash(ignore))] pub ctx: OptimizerContextRef, + pub options: CdcScanOptions, +} + +#[derive(Debug, Clone, Hash, PartialEq)] +pub struct CdcScanOptions { pub disable_backfill: bool, + pub snapshot_barrier_interval: u32, + pub snapshot_batch_size: u32, +} + +impl Default for CdcScanOptions { + fn default() -> Self { + Self { + disable_backfill: false, + snapshot_barrier_interval: 1, + snapshot_batch_size: 1000, + } + } +} + +impl CdcScanOptions { + pub fn from_with_options(with_options: &WithOptions) -> Result { + // unspecified option will use default values + let mut scan_options = Self::default(); + + // disable backfill if 'snapshot=false' + if let Some(snapshot) = with_options.get(CDC_BACKFILL_ENABLE_KEY) { + scan_options.disable_backfill = !(bool::from_str(snapshot) + .map_err(|_| anyhow!("Invalid value for {}", CDC_BACKFILL_ENABLE_KEY))?); + }; + + if let Some(snapshot_interval) = with_options.get(CDC_BACKFILL_SNAPSHOT_INTERVAL_KEY) { + scan_options.snapshot_barrier_interval = u32::from_str(snapshot_interval) + .map_err(|_| anyhow!("Invalid value for {}", CDC_BACKFILL_SNAPSHOT_INTERVAL_KEY))?; + }; + + if let Some(snapshot_batch_size) = with_options.get(CDC_BACKFILL_SNAPSHOT_BATCH_SIZE_KEY) { + scan_options.snapshot_batch_size = + u32::from_str(snapshot_batch_size).map_err(|_| { + anyhow!("Invalid value for {}", CDC_BACKFILL_SNAPSHOT_BATCH_SIZE_KEY) + })?; + }; + + Ok(scan_options) + } + + pub fn to_proto(&self) -> StreamCdcScanOptions { + StreamCdcScanOptions { + disable_backfill: self.disable_backfill, + snapshot_barrier_interval: self.snapshot_barrier_interval, + snapshot_batch_size: self.snapshot_batch_size, + } + } } impl CdcScan { @@ -104,14 +165,14 @@ impl CdcScan { output_col_idx: Vec, // the column index in the table cdc_table_desc: Rc, ctx: OptimizerContextRef, - disable_backfill: bool, + options: CdcScanOptions, ) -> Self { Self { table_name, output_col_idx, cdc_table_desc, ctx, - disable_backfill, + options, } } diff --git a/src/frontend/src/optimizer/plan_node/generic/changelog.rs b/src/frontend/src/optimizer/plan_node/generic/changelog.rs new file mode 100644 index 0000000000000..a3e52ec3580ca --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/generic/changelog.rs @@ -0,0 +1,92 @@ +// Copyright 2024 RisingWave Labs +// +// 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 pretty_xmlish::{Str, XmlNode}; +use risingwave_common::catalog::{Field, Schema}; +use risingwave_common::util::column_index_mapping::ColIndexMapping; + +use super::{DistillUnit, GenericPlanNode}; +use crate::optimizer::plan_node::stream::prelude::GenericPlanRef; +use crate::optimizer::plan_node::utils::childless_record; +use crate::optimizer::property::FunctionalDependencySet; +use crate::utils::ColIndexMappingRewriteExt; +use crate::OptimizerContextRef; + +pub const CHANGELOG_OP: &str = "changelog_op"; +pub const _CHANGELOG_ROW_ID: &str = "_changelog_row_id"; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ChangeLog { + pub input: PlanRef, + // If there is no op in the output result, it is false, example 'create materialized view mv1 as with sub as changelog from t1 select v1 from sub;' + pub need_op: bool, + // Before rewrite. If there is no changelog_row_id in the output result, it is false. + // After rewrite. It is always true. + pub need_changelog_row_id: bool, +} +impl DistillUnit for ChangeLog { + fn distill_with_name<'a>(&self, name: impl Into>) -> XmlNode<'a> { + childless_record(name, vec![]) + } +} +impl ChangeLog { + pub fn new(input: PlanRef, need_op: bool, need_changelog_row_id: bool) -> Self { + ChangeLog { + input, + need_op, + need_changelog_row_id, + } + } + + pub fn i2o_col_mapping(&self) -> ColIndexMapping { + let mut map = vec![None; self.input.schema().len()]; + (0..self.input.schema().len()).for_each(|i| map[i] = Some(i)); + ColIndexMapping::new(map, self.schema().len()) + } +} +impl GenericPlanNode for ChangeLog { + fn schema(&self) -> Schema { + let mut fields = self.input.schema().fields.clone(); + if self.need_op { + fields.push(Field::with_name( + risingwave_common::types::DataType::Int16, + CHANGELOG_OP, + )); + } + if self.need_changelog_row_id { + fields.push(Field::with_name( + risingwave_common::types::DataType::Serial, + _CHANGELOG_ROW_ID, + )); + } + Schema::new(fields) + } + + fn stream_key(&self) -> Option> { + if self.need_changelog_row_id { + let keys = vec![self.schema().len() - 1]; + Some(keys) + } else { + None + } + } + + fn ctx(&self) -> OptimizerContextRef { + self.input.ctx() + } + + fn functional_dependency(&self) -> FunctionalDependencySet { + let i2o = self.i2o_col_mapping(); + i2o.rewrite_functional_dependency_set(self.input.functional_dependency().clone()) + } +} diff --git a/src/frontend/src/optimizer/plan_node/generic/cte_ref.rs b/src/frontend/src/optimizer/plan_node/generic/cte_ref.rs new file mode 100644 index 0000000000000..6414b8dfdc0b2 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/generic/cte_ref.rs @@ -0,0 +1,100 @@ +// Copyright 2024 RisingWave Labs +// +// 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::hash::Hash; + +use itertools::Itertools; +use pretty_xmlish::{Pretty, StrAssocArr}; +use risingwave_common::catalog::Schema; + +use super::{impl_distill_unit_from_fields, GenericPlanNode, GenericPlanRef}; +use crate::binder::ShareId; +use crate::optimizer::property::FunctionalDependencySet; +use crate::{optimizer, OptimizerContextRef}; + +#[derive(Clone, Debug)] +pub struct CteRef { + share_id: ShareId, + base: PlanRef, +} + +impl PartialEq for CteRef { + fn eq(&self, other: &Self) -> bool { + self.share_id == other.share_id + } +} + +impl Eq for CteRef {} + +impl Hash for CteRef { + fn hash(&self, state: &mut H) { + self.share_id.hash(state); + } +} + +impl CteRef { + pub fn new(share_id: ShareId, base: PlanRef) -> Self { + Self { share_id, base } + } +} + +impl CteRef { + pub fn get_cte_ref(&self) -> Option { + self.ctx().get_rcte_cache_plan(&self.share_id) + } +} + +impl GenericPlanNode for CteRef { + fn schema(&self) -> Schema { + if let Some(plan_ref) = self.get_cte_ref() { + plan_ref.schema().clone() + } else { + self.base.schema().clone() + } + } + + fn stream_key(&self) -> Option> { + if let Some(plan_ref) = self.get_cte_ref() { + plan_ref + .stream_key() + .map(|s| s.iter().map(|i| i.to_owned()).collect_vec()) + } else { + self.base + .stream_key() + .map(|s| s.iter().map(|i| i.to_owned()).collect_vec()) + } + } + + fn ctx(&self) -> OptimizerContextRef { + // it does not matter where the context is coming from, + // since we are only getting a reference. + self.base.ctx() + } + + fn functional_dependency(&self) -> FunctionalDependencySet { + if let Some(plan_ref) = self.get_cte_ref() { + plan_ref.functional_dependency().clone() + } else { + self.base.functional_dependency().clone() + } + } +} + +impl CteRef { + pub fn fields_pretty<'a>(&self) -> StrAssocArr<'a> { + vec![("share_id", Pretty::debug(&self.share_id))] + } +} + +impl_distill_unit_from_fields! {CteRef, GenericPlanRef} diff --git a/src/frontend/src/optimizer/plan_node/generic/filter.rs b/src/frontend/src/optimizer/plan_node/generic/filter.rs index 2bc4ee0324689..71d4ca9fd05c1 100644 --- a/src/frontend/src/optimizer/plan_node/generic/filter.rs +++ b/src/frontend/src/optimizer/plan_node/generic/filter.rs @@ -22,10 +22,6 @@ use crate::optimizer::plan_node::utils::childless_record; use crate::optimizer::property::FunctionalDependencySet; use crate::utils::{Condition, ConditionDisplay}; -/// [`Filter`] iterates over its input and returns elements for which `predicate` evaluates to -/// true, filtering out the others. -/// -/// If the condition allows nulls, then a null value is treated the same as false. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Filter { pub predicate: Condition, diff --git a/src/frontend/src/optimizer/plan_node/generic/log_scan.rs b/src/frontend/src/optimizer/plan_node/generic/log_scan.rs new file mode 100644 index 0000000000000..498d4a44b0fcc --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/generic/log_scan.rs @@ -0,0 +1,160 @@ +// Copyright 2024 RisingWave Labs +// +// 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::HashMap; +use std::rc::Rc; + +use educe::Educe; +use pretty_xmlish::Pretty; +use risingwave_common::catalog::{Field, Schema, TableDesc}; +use risingwave_common::types::DataType; +use risingwave_common::util::sort_util::ColumnOrder; + +use crate::catalog::ColumnId; +use crate::optimizer::optimizer_context::OptimizerContextRef; + +const OP_NAME: &str = "op"; +const OP_TYPE: DataType = DataType::Int16; + +#[derive(Debug, Clone, Educe)] +#[educe(PartialEq, Eq, Hash)] +pub struct LogScan { + pub table_name: String, + /// Include `output_col_idx` and `op_column` + pub output_col_idx: Vec, + /// Descriptor of the table + pub table_desc: Rc, + /// Help `RowSeqLogScan` executor use a better chunk size + pub chunk_size: Option, + + #[educe(PartialEq(ignore))] + #[educe(Hash(ignore))] + pub ctx: OptimizerContextRef, + + pub old_epoch: u64, + pub new_epoch: u64, +} + +impl LogScan { + // Used for create batch exec, without op + pub fn output_column_ids(&self) -> Vec { + self.output_col_idx + .iter() + .map(|i| self.table_desc.columns[*i].column_id) + .collect() + } + + pub fn primary_key(&self) -> &[ColumnOrder] { + &self.table_desc.pk + } + + fn column_names_with_table_prefix(&self) -> Vec { + let mut out_column_names: Vec<_> = self + .output_col_idx + .iter() + .map(|&i| format!("{}.{}", self.table_name, self.table_desc.columns[i].name)) + .collect(); + out_column_names.push(format!("{}.{}", self.table_name, OP_NAME)); + out_column_names + } + + pub(crate) fn column_names(&self) -> Vec { + let mut out_column_names: Vec<_> = self + .output_col_idx + .iter() + .map(|&i| self.table_desc.columns[i].name.clone()) + .collect(); + out_column_names.push(OP_NAME.to_string()); + out_column_names + } + + pub fn distribution_key(&self) -> Option> { + let tb_idx_to_op_idx = self + .output_col_idx + .iter() + .enumerate() + .map(|(op_idx, tb_idx)| (*tb_idx, op_idx)) + .collect::>(); + self.table_desc + .distribution_key + .iter() + .map(|&tb_idx| tb_idx_to_op_idx.get(&tb_idx).cloned()) + .collect() + } + + /// Create a logical scan node for log table scan + pub(crate) fn new( + table_name: String, + output_col_idx: Vec, + table_desc: Rc, + ctx: OptimizerContextRef, + old_epoch: u64, + new_epoch: u64, + ) -> Self { + Self { + table_name, + output_col_idx, + table_desc, + chunk_size: None, + ctx, + old_epoch, + new_epoch, + } + } + + pub(crate) fn columns_pretty<'a>(&self, verbose: bool) -> Pretty<'a> { + Pretty::Array( + match verbose { + true => self.column_names_with_table_prefix(), + false => self.column_names(), + } + .into_iter() + .map(Pretty::from) + .collect(), + ) + } + + pub(crate) fn schema(&self) -> Schema { + let mut fields: Vec<_> = self + .output_col_idx + .iter() + .map(|tb_idx| { + let col = &self.table_desc.columns[*tb_idx]; + Field::from_with_table_name_prefix(col, &self.table_name) + }) + .collect(); + fields.push(Field::with_name( + OP_TYPE, + format!("{}.{}", &self.table_name, OP_NAME), + )); + Schema { fields } + } + + pub(crate) fn schema_without_table_name(&self) -> Schema { + let mut fields: Vec<_> = self + .output_col_idx + .iter() + .map(|tb_idx| { + let col = &self.table_desc.columns[*tb_idx]; + Field::from(col) + }) + .collect(); + fields.push(Field::with_name(OP_TYPE, OP_NAME)); + Schema { fields } + } + + pub(crate) fn ctx(&self) -> OptimizerContextRef { + self.ctx.clone() + } +} diff --git a/src/frontend/src/optimizer/plan_node/generic/mod.rs b/src/frontend/src/optimizer/plan_node/generic/mod.rs index 5154c84017b87..38efb6fe2a27d 100644 --- a/src/frontend/src/optimizer/plan_node/generic/mod.rs +++ b/src/frontend/src/optimizer/plan_node/generic/mod.rs @@ -44,6 +44,8 @@ mod table_scan; pub use table_scan::*; mod sys_scan; pub use sys_scan::*; +mod log_scan; +pub use log_scan::*; mod cdc_scan; pub use cdc_scan::*; @@ -72,6 +74,14 @@ mod limit; pub use limit::*; mod max_one_row; pub use max_one_row::*; +mod cte_ref; +pub use cte_ref::*; +mod recursive_union; +pub use recursive_union::*; +mod changelog; +pub use changelog::*; +mod now; +pub use now::*; pub trait DistillUnit { fn distill_with_name<'a>(&self, name: impl Into>) -> XmlNode<'a>; diff --git a/src/frontend/src/optimizer/plan_node/generic/now.rs b/src/frontend/src/optimizer/plan_node/generic/now.rs new file mode 100644 index 0000000000000..911217d064214 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/generic/now.rs @@ -0,0 +1,106 @@ +// Copyright 2024 RisingWave Labs +// +// 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 educe::Educe; +use enum_as_inner::EnumAsInner; +use pretty_xmlish::{Pretty, Str, XmlNode}; +use risingwave_common::catalog::{Field, Schema}; +use risingwave_common::types::{DataType, Interval, Timestamptz}; + +use super::{DistillUnit, GenericPlanNode}; +use crate::optimizer::plan_node::utils::childless_record; +use crate::optimizer::property::FunctionalDependencySet; +use crate::OptimizerContextRef; + +#[derive(Debug, Clone, Educe)] +#[educe(PartialEq, Eq, Hash)] +pub struct Now { + #[educe(PartialEq(ignore))] + #[educe(Hash(ignore))] + pub ctx: OptimizerContextRef, + + pub mode: Mode, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, EnumAsInner)] +pub enum Mode { + /// Emit current timestamp on startup, update it on barrier. + UpdateCurrent, + /// Generate a series of timestamps starting from `start_timestamp` with `interval`. + /// Keep generating new timestamps on barrier. + GenerateSeries { + start_timestamp: Timestamptz, + interval: Interval, + }, +} + +impl GenericPlanNode for Now { + fn functional_dependency(&self) -> crate::optimizer::property::FunctionalDependencySet { + FunctionalDependencySet::new(1) // only one column and no dependency + } + + fn schema(&self) -> risingwave_common::catalog::Schema { + Schema::new(vec![Field { + data_type: DataType::Timestamptz, + name: String::from(if self.mode.is_update_current() { + "now" + } else { + "ts" + }), + sub_fields: vec![], + type_name: String::default(), + }]) + } + + fn stream_key(&self) -> Option> { + match self.mode { + Mode::UpdateCurrent => Some(vec![]), + Mode::GenerateSeries { .. } => Some(vec![0]), + } + } + + fn ctx(&self) -> OptimizerContextRef { + self.ctx.clone() + } +} + +impl Now { + pub fn update_current(ctx: OptimizerContextRef) -> Self { + Self::new_inner(ctx, Mode::UpdateCurrent) + } + + pub fn generate_series( + ctx: OptimizerContextRef, + start_timestamp: Timestamptz, + interval: Interval, + ) -> Self { + Self::new_inner( + ctx, + Mode::GenerateSeries { + start_timestamp, + interval, + }, + ) + } + + fn new_inner(ctx: OptimizerContextRef, mode: Mode) -> Self { + Self { ctx, mode } + } +} + +impl DistillUnit for Now { + fn distill_with_name<'a>(&self, name: impl Into>) -> XmlNode<'a> { + childless_record(name, vec![("mode", Pretty::debug(&self.mode))]) + } +} diff --git a/src/frontend/src/optimizer/plan_node/generic/project.rs b/src/frontend/src/optimizer/plan_node/generic/project.rs index 10329c9f8ca91..68c652f0e006f 100644 --- a/src/frontend/src/optimizer/plan_node/generic/project.rs +++ b/src/frontend/src/optimizer/plan_node/generic/project.rs @@ -86,7 +86,7 @@ impl GenericPlanNode for Project { Some(input_idx) => { let mut field = input_schema.fields()[input_idx].clone(); if let Some(name) = self.field_names.get(&i) { - field.name = name.clone(); + field.name.clone_from(name); } (field.name, field.sub_fields, field.type_name) } diff --git a/src/frontend/src/optimizer/plan_node/generic/recursive_union.rs b/src/frontend/src/optimizer/plan_node/generic/recursive_union.rs new file mode 100644 index 0000000000000..023718f1a9682 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/generic/recursive_union.rs @@ -0,0 +1,64 @@ +// Copyright 2024 RisingWave Labs +// +// 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 pretty_xmlish::StrAssocArr; +use risingwave_common::catalog::Schema; + +use super::{impl_distill_unit_from_fields, GenericPlanNode, GenericPlanRef}; +use crate::binder::ShareId; +use crate::optimizer::property::FunctionalDependencySet; +use crate::OptimizerContextRef; + +/// `RecursiveUnion` returns the union of the rows of its inputs. +/// note: if `all` is false, it needs to eliminate duplicates. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecursiveUnion { + pub id: ShareId, + pub base: PlanRef, + pub recursive: PlanRef, +} + +impl GenericPlanNode for RecursiveUnion { + fn functional_dependency(&self) -> FunctionalDependencySet { + self.recursive.functional_dependency().clone() + } + + fn schema(&self) -> Schema { + self.recursive.schema().clone() + } + + fn stream_key(&self) -> Option> { + let fields_len = self.base.schema().len(); + let base = self.base.stream_key(); + if let Some(base) = base { + let mut base = base.to_vec(); + base.push(fields_len); + Some(base) + } else { + None + } + } + + fn ctx(&self) -> OptimizerContextRef { + self.recursive.ctx() + } +} + +impl RecursiveUnion { + pub fn fields_pretty<'a>(&self) -> StrAssocArr<'a> { + vec![] + } +} + +impl_distill_unit_from_fields!(RecursiveUnion, GenericPlanRef); diff --git a/src/frontend/src/optimizer/plan_node/generic/source.rs b/src/frontend/src/optimizer/plan_node/generic/source.rs index 40bd376dd46ca..af138f7dd80a5 100644 --- a/src/frontend/src/optimizer/plan_node/generic/source.rs +++ b/src/frontend/src/optimizer/plan_node/generic/source.rs @@ -137,13 +137,27 @@ impl Source { (self, original_row_id_index) } - pub fn infer_internal_table_catalog(require_dist_key: bool) -> TableCatalog { - // note that source's internal table is to store partition_id -> offset mapping and its - // schema is irrelevant to input schema - // On the premise of ensuring that the materialized_source data can be cleaned up, keep the - // state in source. - // Source state doesn't maintain retention_seconds, internal_table_subset function only - // returns retention_seconds so default is used here + /// Source's state table is `partition_id -> offset_info`. + /// Its schema is irrelevant to the data's schema. + /// + /// ## Notes on the distribution of the state table (`is_distributed`) + /// + /// Source executors are always distributed, but their state tables are special. + /// + /// ### `StreamSourceExecutor`: singleton (only one vnode) + /// + /// Its states are not sharded by consistent hash. + /// + /// Each actor accesses (point get) some partitions (a.k.a splits). + /// They are assigned by `SourceManager` in meta, + /// instead of `vnode` computed from the `partition_id`. + /// + /// ### `StreamFsFetch`: distributed by `partition_id` + /// + /// Each actor accesses (range scan) splits according to the `vnode` + /// computed from `partition_id`. + /// This is a normal distributed table. + pub fn infer_internal_table_catalog(is_distributed: bool) -> TableCatalog { let mut builder = TableCatalogBuilder::default(); let key = Field { @@ -164,7 +178,7 @@ impl Source { builder.add_order_column(ordered_col_idx, OrderType::ascending()); builder.build( - if require_dist_key { + if is_distributed { vec![ordered_col_idx] } else { vec![] diff --git a/src/frontend/src/optimizer/plan_node/logical_agg.rs b/src/frontend/src/optimizer/plan_node/logical_agg.rs index cad073386a42e..f32b37d250b5a 100644 --- a/src/frontend/src/optimizer/plan_node/logical_agg.rs +++ b/src/frontend/src/optimizer/plan_node/logical_agg.rs @@ -18,7 +18,6 @@ use risingwave_common::types::{DataType, Datum, ScalarImpl}; use risingwave_common::util::sort_util::ColumnOrder; use risingwave_common::{bail_not_implemented, not_implemented}; use risingwave_expr::aggregate::{agg_kinds, AggKind}; -use risingwave_expr::sig::FUNCTION_REGISTRY; use super::generic::{self, Agg, GenericPlanRef, PlanAggCall, ProjectBuilder}; use super::utils::impl_distill_by_unit; @@ -27,7 +26,7 @@ use super::{ PlanTreeNodeUnary, PredicatePushdown, StreamHashAgg, StreamProject, StreamSimpleAgg, StreamStatelessSimpleAgg, ToBatch, ToStream, }; -use crate::error::{ErrorCode, Result}; +use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{ AggCall, Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, FunctionCall, InputRef, Literal, OrderBy, WindowFunction, @@ -262,7 +261,7 @@ pub struct LogicalAggBuilder { /// the agg calls agg_calls: Vec, /// the error during the expression rewriting - error: Option, + error: Option, /// If `is_in_filter_clause` is true, it means that /// we are processing filter clause. /// This field is needed because input refs in these clauses @@ -354,7 +353,7 @@ impl LogicalAggBuilder { fn rewrite_with_error(&mut self, expr: ExprImpl) -> Result { let rewritten_expr = self.rewrite_expr(expr); if let Some(error) = self.error.take() { - return Err(error.into()); + return Err(error); } Ok(rewritten_expr) } @@ -377,51 +376,159 @@ impl LogicalAggBuilder { self.group_key.len() } - /// Push a new planned agg call into the builder. - /// Return an `InputRef` to that agg call. - /// For existing agg calls, return an `InputRef` to the existing one. - fn push_agg_call(&mut self, agg_call: PlanAggCall) -> InputRef { - if let Some((pos, existing)) = self.agg_calls.iter().find_position(|&c| c == &agg_call) { - return InputRef::new( - self.schema_agg_start_offset() + pos, - existing.return_type.clone(), - ); - } - let index = self.schema_agg_start_offset() + self.agg_calls.len(); - let data_type = agg_call.return_type.clone(); - self.agg_calls.push(agg_call); - InputRef::new(index, data_type) - } - - /// When there is an agg call, there are 3 things to do: - /// 1. eval its inputs via project; - /// 2. add a `PlanAggCall` to agg; - /// 3. rewrite it as an `InputRef` to the agg result in select list. - /// - /// Note that the rewriter does not traverse into inputs of agg calls. - fn try_rewrite_agg_call( - &mut self, + /// Rewrite [`AggCall`] if needed, and push it into the builder using `push_agg_call`. + /// This is shared by [`LogicalAggBuilder`] and `LogicalOverWindowBuilder`. + pub(crate) fn general_rewrite_agg_call( agg_call: AggCall, - ) -> std::result::Result { - let return_type = agg_call.return_type(); - let (agg_kind, inputs, mut distinct, mut order_by, filter, direct_args) = - agg_call.decompose(); + mut push_agg_call: impl FnMut(AggCall) -> Result, + ) -> Result { + match agg_call.agg_kind { + // Rewrite avg to cast(sum as avg_return_type) / count. + AggKind::Avg => { + assert_eq!(agg_call.args.len(), 1); + + let sum = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Sum, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?) + .cast_explicit(agg_call.return_type())?; + + let count = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Count, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?); + + Ok(FunctionCall::new(ExprType::Divide, Vec::from([sum, count]))?.into()) + } + // We compute `var_samp` as + // (sum(sq) - sum * sum / count) / (count - 1) + // and `var_pop` as + // (sum(sq) - sum * sum / count) / count + // Since we don't have the square function, we use the plain Multiply for squaring, + // which is in a sense more general than the pow function, especially when calculating + // covariances in the future. Also we don't have the sqrt function for rooting, so we + // use pow(x, 0.5) to simulate + kind @ (AggKind::StddevPop + | AggKind::StddevSamp + | AggKind::VarPop + | AggKind::VarSamp) => { + let arg = agg_call.args().iter().exactly_one().unwrap(); + let squared_arg = ExprImpl::from(FunctionCall::new( + ExprType::Multiply, + vec![arg.clone(), arg.clone()], + )?); + + let sum_of_sq = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Sum, + vec![squared_arg], + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?) + .cast_explicit(agg_call.return_type())?; + + let sum = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Sum, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?) + .cast_explicit(agg_call.return_type())?; + + let count = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Count, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?); + + let one = ExprImpl::from(Literal::new( + Datum::from(ScalarImpl::Int64(1)), + DataType::Int64, + )); - if matches!(agg_kind, agg_kinds::must_have_order_by!()) && order_by.sort_exprs.is_empty() { - return Err(ErrorCode::InvalidInputSyntax(format!( - "Aggregation function {} requires ORDER BY clause", - agg_kind - ))); - } + let squared_sum = ExprImpl::from(FunctionCall::new( + ExprType::Multiply, + vec![sum.clone(), sum], + )?); + + let numerator = ExprImpl::from(FunctionCall::new( + ExprType::Subtract, + vec![ + sum_of_sq, + ExprImpl::from(FunctionCall::new( + ExprType::Divide, + vec![squared_sum, count.clone()], + )?), + ], + )?); + + let denominator = match kind { + AggKind::VarPop | AggKind::StddevPop => count.clone(), + AggKind::VarSamp | AggKind::StddevSamp => ExprImpl::from(FunctionCall::new( + ExprType::Subtract, + vec![count.clone(), one.clone()], + )?), + _ => unreachable!(), + }; - // try ignore ORDER BY if it doesn't affect the result - if matches!(agg_kind, agg_kinds::result_unaffected_by_order_by!()) { - order_by = OrderBy::any(); - } - // try ignore DISTINCT if it doesn't affect the result - if matches!(agg_kind, agg_kinds::result_unaffected_by_distinct!()) { - distinct = false; + let mut target = ExprImpl::from(FunctionCall::new( + ExprType::Divide, + vec![numerator, denominator], + )?); + + if matches!(kind, AggKind::StddevPop | AggKind::StddevSamp) { + target = ExprImpl::from(FunctionCall::new(ExprType::Sqrt, vec![target])?); + } + + match kind { + AggKind::VarPop | AggKind::StddevPop => Ok(target), + AggKind::StddevSamp | AggKind::VarSamp => { + let case_cond = ExprImpl::from(FunctionCall::new( + ExprType::LessThanOrEqual, + vec![count, one], + )?); + let null = ExprImpl::from(Literal::new(None, agg_call.return_type())); + + Ok(ExprImpl::from(FunctionCall::new( + ExprType::Case, + vec![case_cond, null, target], + )?)) + } + _ => unreachable!(), + } + } + _ => Ok(push_agg_call(agg_call)?.into()), } + } + + /// Push a new agg call into the builder. + /// Return an `InputRef` to that agg call. + /// For existing agg calls, return an `InputRef` to the existing one. + fn push_agg_call(&mut self, agg_call: AggCall) -> Result { + let AggCall { + agg_kind, + return_type, + args, + distinct, + order_by, + filter, + direct_args, + user_defined, + } = agg_call; self.is_in_filter_clause = true; // filter expr is not added to `input_proj_builder` as a whole. Special exprs incl @@ -429,27 +536,7 @@ impl LogicalAggBuilder { let filter = filter.rewrite_expr(self); self.is_in_filter_clause = false; - if matches!(agg_kind, AggKind::Grouping) { - if self.grouping_sets.is_empty() { - return Err(ErrorCode::NotSupported( - "GROUPING must be used in a query with grouping sets".into(), - "try to use grouping sets instead".into(), - )); - } - if inputs.len() >= 32 { - return Err(ErrorCode::InvalidInputSyntax( - "GROUPING must have fewer than 32 arguments".into(), - )); - } - if inputs.iter().any(|x| self.try_as_group_expr(x).is_none()) { - return Err(ErrorCode::InvalidInputSyntax( - "arguments to GROUPING must be grouping expressions of the associated query level" - .into(), - )); - } - } - - let inputs: Vec<_> = inputs + let args: Vec<_> = args .iter() .map(|expr| { let index = self.input_proj_builder.add_expr(expr)?; @@ -470,220 +557,91 @@ impl LogicalAggBuilder { not_implemented!("{err} inside aggregation calls order by") })?; - match agg_kind { - // Rewrite avg to cast(sum as avg_return_type) / count. - AggKind::Avg => { - assert_eq!(inputs.len(), 1); - - let left_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Sum, &[inputs[0].return_type()]) - .unwrap(); - let left_ref = self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Sum, - return_type: left_return_type, - inputs: inputs.clone(), - distinct, - order_by: order_by.clone(), - filter: filter.clone(), - direct_args: direct_args.clone(), - }); - let left = ExprImpl::from(left_ref).cast_explicit(return_type).unwrap(); - - let right_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Count, &[inputs[0].return_type()]) - .unwrap(); - let right_ref = self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Count, - return_type: right_return_type, - inputs, - distinct, - order_by, - filter, - direct_args, - }); - - Ok(ExprImpl::from( - FunctionCall::new(ExprType::Divide, vec![left, right_ref.into()]).unwrap(), - )) - } + let plan_agg_call = PlanAggCall { + agg_kind, + return_type: return_type.clone(), + inputs: args, + distinct, + order_by, + filter, + direct_args, + user_defined, + }; - // We compute `var_samp` as - // (sum(sq) - sum * sum / count) / (count - 1) - // and `var_pop` as - // (sum(sq) - sum * sum / count) / count - // Since we don't have the square function, we use the plain Multiply for squaring, - // which is in a sense more general than the pow function, especially when calculating - // covariances in the future. Also we don't have the sqrt function for rooting, so we - // use pow(x, 0.5) to simulate - AggKind::StddevPop | AggKind::StddevSamp | AggKind::VarPop | AggKind::VarSamp => { - let input = inputs.iter().exactly_one().unwrap(); - let pre_proj_input = self.input_proj_builder.get_expr(input.index).unwrap(); - - // first, we compute sum of squared as sum_sq - let squared_input_expr = ExprImpl::from( - FunctionCall::new( - ExprType::Multiply, - vec![pre_proj_input.clone(), pre_proj_input.clone()], - ) - .unwrap(), - ); - - let squared_input_proj_index = self - .input_proj_builder - .add_expr(&squared_input_expr) - .unwrap(); - - let sum_of_squares_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Sum, &[squared_input_expr.return_type()]) - .unwrap(); - - let sum_of_squares_expr = ExprImpl::from(self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Sum, - return_type: sum_of_squares_return_type, - inputs: vec![InputRef::new( - squared_input_proj_index, - squared_input_expr.return_type(), - )], - distinct, - order_by: order_by.clone(), - filter: filter.clone(), - direct_args: direct_args.clone(), - })) - .cast_explicit(return_type.clone()) - .unwrap(); - - // after that, we compute sum - let sum_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Sum, &[input.return_type()]) - .unwrap(); - - let sum_expr = ExprImpl::from(self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Sum, - return_type: sum_return_type, - inputs: inputs.clone(), - distinct, - order_by: order_by.clone(), - filter: filter.clone(), - direct_args: direct_args.clone(), - })) - .cast_explicit(return_type.clone()) - .unwrap(); - - // then, we compute count - let count_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Count, &[input.return_type()]) - .unwrap(); - - let count_expr = ExprImpl::from(self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Count, - return_type: count_return_type, - inputs, - distinct, - order_by, - filter, - direct_args, - })); - - // we start with variance - - // sum * sum - let square_of_sum_expr = ExprImpl::from( - FunctionCall::new(ExprType::Multiply, vec![sum_expr.clone(), sum_expr]) - .unwrap(), - ); - - // sum_sq - sum * sum / count - let numerator_expr = ExprImpl::from( - FunctionCall::new( - ExprType::Subtract, - vec![ - sum_of_squares_expr, - ExprImpl::from( - FunctionCall::new( - ExprType::Divide, - vec![square_of_sum_expr, count_expr.clone()], - ) - .unwrap(), - ), - ], - ) - .unwrap(), - ); - - // count or count - 1 - let denominator_expr = match agg_kind { - AggKind::StddevPop | AggKind::VarPop => count_expr.clone(), - AggKind::StddevSamp | AggKind::VarSamp => ExprImpl::from( - FunctionCall::new( - ExprType::Subtract, - vec![ - count_expr.clone(), - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - ) - .unwrap(), - ), - _ => unreachable!(), - }; + if let Some((pos, existing)) = self + .agg_calls + .iter() + .find_position(|&c| c == &plan_agg_call) + { + return Ok(InputRef::new( + self.schema_agg_start_offset() + pos, + existing.return_type.clone(), + )); + } + let index = self.schema_agg_start_offset() + self.agg_calls.len(); + self.agg_calls.push(plan_agg_call); + Ok(InputRef::new(index, return_type)) + } - let mut target_expr = ExprImpl::from( - FunctionCall::new(ExprType::Divide, vec![numerator_expr, denominator_expr]) - .unwrap(), - ); + /// When there is an agg call, there are 3 things to do: + /// 1. Rewrite `avg`, `var_samp`, etc. into a combination of `sum`, `count`, etc.; + /// 2. Add exprs in arguments to input `Project`; + /// 2. Add the agg call to current `Agg`, and return an `InputRef` to it. + /// + /// Note that the rewriter does not traverse into inputs of agg calls. + fn try_rewrite_agg_call(&mut self, mut agg_call: AggCall) -> Result { + if matches!(agg_call.agg_kind, agg_kinds::must_have_order_by!()) + && agg_call.order_by.sort_exprs.is_empty() + { + return Err(ErrorCode::InvalidInputSyntax(format!( + "Aggregation function {} requires ORDER BY clause", + agg_call.agg_kind + )) + .into()); + } - // stddev = sqrt(variance) - if matches!(agg_kind, AggKind::StddevPop | AggKind::StddevSamp) { - target_expr = ExprImpl::from( - FunctionCall::new(ExprType::Sqrt, vec![target_expr]).unwrap(), - ); - } + // try ignore ORDER BY if it doesn't affect the result + if matches!( + agg_call.agg_kind, + agg_kinds::result_unaffected_by_order_by!() + ) { + agg_call.order_by = OrderBy::any(); + } + // try ignore DISTINCT if it doesn't affect the result + if matches!( + agg_call.agg_kind, + agg_kinds::result_unaffected_by_distinct!() + ) { + agg_call.distinct = false; + } - match agg_kind { - AggKind::VarPop | AggKind::StddevPop => Ok(target_expr), - AggKind::StddevSamp | AggKind::VarSamp => { - let less_than_expr = ExprImpl::from( - FunctionCall::new( - ExprType::LessThanOrEqual, - vec![ - count_expr, - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - ) - .unwrap(), - ); - let null_expr = ExprImpl::from(Literal::new(None, return_type)); - - let case_expr = ExprImpl::from( - FunctionCall::new( - ExprType::Case, - vec![less_than_expr, null_expr, target_expr], - ) - .unwrap(), - ); - - Ok(case_expr) - } - _ => unreachable!(), - } + if matches!(agg_call.agg_kind, AggKind::Grouping) { + if self.grouping_sets.is_empty() { + return Err(ErrorCode::NotSupported( + "GROUPING must be used in a query with grouping sets".into(), + "try to use grouping sets instead".into(), + ) + .into()); + } + if agg_call.args.len() >= 32 { + return Err(ErrorCode::InvalidInputSyntax( + "GROUPING must have fewer than 32 arguments".into(), + ) + .into()); + } + if agg_call + .args + .iter() + .any(|x| self.try_as_group_expr(x).is_none()) + { + return Err(ErrorCode::InvalidInputSyntax( + "arguments to GROUPING must be grouping expressions of the associated query level" + .into(), + ).into()); } - _ => Ok(self - .push_agg_call(PlanAggCall { - agg_kind, - return_type, - inputs, - distinct, - order_by, - filter, - direct_args, - }) - .into()), } + + Self::general_rewrite_agg_call(agg_call, |agg_call| self.push_agg_call(agg_call)) } } @@ -754,10 +712,13 @@ impl ExprRewriter for LogicalAggBuilder { ) .into() } else { - self.error = Some(ErrorCode::InvalidInputSyntax( - "column must appear in the GROUP BY clause or be used in an aggregate function" - .into(), - )); + self.error = Some( + ErrorCode::InvalidInputSyntax( + "column must appear in the GROUP BY clause or be used in an aggregate function" + .into(), + ) + .into(), + ); expr } } @@ -1179,10 +1140,16 @@ impl ToStream for LogicalAgg { Ok(plan) } else { // a `count(*)` is appended, should project the output + assert_eq!(self.agg_calls().len() + 1, n_final_agg_calls); Ok(StreamProject::new(generic::Project::with_out_col_idx( plan, 0..self.schema().len(), )) + // If there's no agg call, then `count(*)` will be the only column in the output besides keys. + // Since it'll be pruned immediately in `StreamProject`, the update records are likely to be + // no-op. So we set the hint to instruct the executor to eliminate them. + // See https://github.com/risingwavelabs/risingwave/issues/17030. + .with_noop_update_hint(self.agg_calls().is_empty()) .into()) } } @@ -1202,12 +1169,9 @@ impl ToStream for LogicalAgg { #[cfg(test)] mod tests { use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::DataType; use super::*; - use crate::expr::{ - assert_eq_input_ref, input_ref_to_column_indices, AggCall, ExprType, FunctionCall, OrderBy, - }; + use crate::expr::{assert_eq_input_ref, input_ref_to_column_indices}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; @@ -1378,6 +1342,7 @@ mod tests { order_by: vec![], filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }; Agg::new(vec![agg_call], vec![1].into(), values.into()).into() } @@ -1498,6 +1463,7 @@ mod tests { order_by: vec![], filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }; let agg: PlanRef = Agg::new(vec![agg_call], vec![1].into(), values.into()).into(); @@ -1562,6 +1528,7 @@ mod tests { order_by: vec![], filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }, PlanAggCall { agg_kind: AggKind::Max, @@ -1571,6 +1538,7 @@ mod tests { order_by: vec![], filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }, ]; let agg: PlanRef = Agg::new(agg_calls, vec![1, 2].into(), values.into()).into(); diff --git a/src/frontend/src/optimizer/plan_node/logical_apply.rs b/src/frontend/src/optimizer/plan_node/logical_apply.rs index b7a564ee32982..9b11927a1c4e4 100644 --- a/src/frontend/src/optimizer/plan_node/logical_apply.rs +++ b/src/frontend/src/optimizer/plan_node/logical_apply.rs @@ -53,6 +53,10 @@ pub struct LogicalApply { /// Whether we require the subquery to produce at most one row. If `true`, we have to report an /// error if the subquery produces more than one row. max_one_row: bool, + + /// An apply has been translated by `translate_apply()`, so we should not translate it in `translate_apply_rule` again. + /// This flag is used to avoid infinite loop in General Unnesting(Translate Apply), since we use a top-down apply order instead of bottom-up to improve the multi-scalar subqueries optimization time. + translated: bool, } impl Distill for LogicalApply { @@ -85,6 +89,7 @@ impl LogicalApply { correlated_id: CorrelatedId, correlated_indices: Vec, max_one_row: bool, + translated: bool, ) -> Self { let ctx = left.ctx(); let join_core = generic::Join::with_full_output(left, right, join_type, on); @@ -105,6 +110,7 @@ impl LogicalApply { correlated_id, correlated_indices, max_one_row, + translated, } } @@ -125,6 +131,7 @@ impl LogicalApply { correlated_id, correlated_indices, max_one_row, + false, ) .into() } @@ -164,6 +171,10 @@ impl LogicalApply { self.correlated_indices.to_owned() } + pub fn translated(&self) -> bool { + self.translated + } + pub fn max_one_row(&self) -> bool { self.max_one_row } @@ -202,7 +213,7 @@ impl LogicalApply { let apply_left_len = apply_left.schema().len(); let correlated_indices_len = correlated_indices.len(); - let new_apply = LogicalApply::create( + let new_apply = LogicalApply::new( domain, apply_right, JoinType::Inner, @@ -210,7 +221,9 @@ impl LogicalApply { correlated_id, correlated_indices, max_one_row, - ); + true, + ) + .into(); let on = Self::rewrite_on(on, correlated_indices_len, apply_left_len).and(Condition { conjunctions: eq_predicates, @@ -285,6 +298,7 @@ impl PlanTreeNodeBinary for LogicalApply { self.correlated_id, self.correlated_indices.clone(), self.max_one_row, + self.translated, ) } } diff --git a/src/frontend/src/optimizer/plan_node/logical_cdc_scan.rs b/src/frontend/src/optimizer/plan_node/logical_cdc_scan.rs index 43ce91bd0dc21..5e9d6467656f1 100644 --- a/src/frontend/src/optimizer/plan_node/logical_cdc_scan.rs +++ b/src/frontend/src/optimizer/plan_node/logical_cdc_scan.rs @@ -28,6 +28,7 @@ use crate::error::Result; use crate::expr::{ExprRewriter, ExprVisitor}; use crate::optimizer::optimizer_context::OptimizerContextRef; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; +use crate::optimizer::plan_node::generic::CdcScanOptions; use crate::optimizer::plan_node::{ ColumnPruningContext, PredicatePushdownContext, RewriteStreamContext, StreamCdcTableScan, ToStreamContext, @@ -60,14 +61,14 @@ impl LogicalCdcScan { table_name: String, // explain-only cdc_table_desc: Rc, ctx: OptimizerContextRef, - disable_backfill: bool, + options: CdcScanOptions, ) -> Self { generic::CdcScan::new( table_name, (0..cdc_table_desc.columns.len()).collect(), cdc_table_desc, ctx, - disable_backfill, + options, ) .into() } @@ -96,7 +97,7 @@ impl LogicalCdcScan { output_col_idx, self.core.cdc_table_desc.clone(), self.base.ctx().clone(), - self.core.disable_backfill, + self.core.options.clone(), ) .into() } diff --git a/src/frontend/src/optimizer/plan_node/logical_changelog.rs b/src/frontend/src/optimizer/plan_node/logical_changelog.rs new file mode 100644 index 0000000000000..f857dc245f0f9 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/logical_changelog.rs @@ -0,0 +1,182 @@ +// Copyright 2024 RisingWave Labs +// +// 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 itertools::Itertools; + +use super::expr_visitable::ExprVisitable; +use super::generic::{GenericPlanRef, CHANGELOG_OP, _CHANGELOG_ROW_ID}; +use super::utils::impl_distill_by_unit; +use super::{ + gen_filter_and_pushdown, generic, ColPrunable, ColumnPruningContext, ExprRewritable, Logical, + LogicalProject, PlanBase, PlanTreeNodeUnary, PredicatePushdown, RewriteStreamContext, + StreamChangeLog, StreamRowIdGen, ToBatch, ToStream, ToStreamContext, +}; +use crate::error::ErrorCode::BindError; +use crate::error::Result; +use crate::expr::{ExprImpl, InputRef}; +use crate::optimizer::property::Distribution; +use crate::utils::{ColIndexMapping, Condition}; +use crate::PlanRef; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LogicalChangeLog { + pub base: PlanBase, + core: generic::ChangeLog, +} + +impl LogicalChangeLog { + pub fn create(input: PlanRef) -> PlanRef { + Self::new(input, true, true).into() + } + + pub fn new(input: PlanRef, need_op: bool, need_changelog_row_id: bool) -> Self { + let core = generic::ChangeLog::new(input, need_op, need_changelog_row_id); + Self::with_core(core) + } + + pub fn with_core(core: generic::ChangeLog) -> Self { + let base = PlanBase::new_logical_with_core(&core); + LogicalChangeLog { base, core } + } +} + +impl PlanTreeNodeUnary for LogicalChangeLog { + fn input(&self) -> PlanRef { + self.core.input.clone() + } + + fn clone_with_input(&self, input: PlanRef) -> Self { + Self::new(input, self.core.need_op, self.core.need_changelog_row_id) + } + + fn rewrite_with_input( + &self, + input: PlanRef, + input_col_change: ColIndexMapping, + ) -> (Self, ColIndexMapping) { + let changelog = Self::new(input, self.core.need_op, true); + + let out_col_change = if self.core.need_op { + let (mut output_vec, len) = input_col_change.into_parts(); + output_vec.push(Some(len)); + ColIndexMapping::new(output_vec, len + 1) + } else { + input_col_change + }; + + let (mut output_vec, len) = out_col_change.into_parts(); + let out_col_change = if self.core.need_changelog_row_id { + output_vec.push(Some(len)); + ColIndexMapping::new(output_vec, len + 1) + } else { + ColIndexMapping::new(output_vec, len + 1) + }; + + (changelog, out_col_change) + } +} + +impl_plan_tree_node_for_unary! {LogicalChangeLog} +impl_distill_by_unit!(LogicalChangeLog, core, "LogicalChangeLog"); + +impl ExprRewritable for LogicalChangeLog {} + +impl ExprVisitable for LogicalChangeLog {} + +impl PredicatePushdown for LogicalChangeLog { + fn predicate_pushdown( + &self, + predicate: Condition, + ctx: &mut super::PredicatePushdownContext, + ) -> PlanRef { + gen_filter_and_pushdown(self, predicate, Condition::true_cond(), ctx) + } +} + +impl ColPrunable for LogicalChangeLog { + fn prune_col(&self, required_cols: &[usize], ctx: &mut ColumnPruningContext) -> PlanRef { + let fields = self.schema().fields(); + let mut need_op = false; + let mut need_changelog_row_id = false; + let new_required_cols: Vec<_> = required_cols + .iter() + .filter_map(|a| { + if let Some(f) = fields.get(*a) { + if f.name == CHANGELOG_OP { + need_op = true; + None + } else if f.name == _CHANGELOG_ROW_ID { + need_changelog_row_id = true; + None + } else { + Some(*a) + } + } else { + Some(*a) + } + }) + .collect(); + + let new_input = self.input().prune_col(&new_required_cols, ctx); + Self::new(new_input, need_op, need_changelog_row_id).into() + } +} + +impl ToBatch for LogicalChangeLog { + fn to_batch(&self) -> Result { + Err(BindError("With changelog cte only support with create mv/sink".to_string()).into()) + } +} + +impl ToStream for LogicalChangeLog { + fn to_stream(&self, ctx: &mut ToStreamContext) -> Result { + let new_input = self.input().to_stream(ctx)?; + + let mut new_logical = self.core.clone(); + new_logical.input = new_input; + let plan = StreamChangeLog::new(new_logical).into(); + let row_id_index = self.schema().fields().len() - 1; + let plan = StreamRowIdGen::new_with_dist( + plan, + row_id_index, + Distribution::HashShard(vec![row_id_index]), + ) + .into(); + + Ok(plan) + } + + fn logical_rewrite_for_stream( + &self, + ctx: &mut RewriteStreamContext, + ) -> Result<(PlanRef, ColIndexMapping)> { + let original_schema = self.input().schema().clone(); + let (input, input_col_change) = self.input().logical_rewrite_for_stream(ctx)?; + let exprs = (0..original_schema.len()) + .map(|x| { + ExprImpl::InputRef( + InputRef::new( + input_col_change.map(x), + original_schema.fields[x].data_type.clone(), + ) + .into(), + ) + }) + .collect_vec(); + let project = LogicalProject::new(input.clone(), exprs); + let (project, out_col_change) = project.rewrite_with_input(input, input_col_change); + let (changelog, out_col_change) = self.rewrite_with_input(project.into(), out_col_change); + Ok((changelog.into(), out_col_change)) + } +} diff --git a/src/frontend/src/optimizer/plan_node/logical_cte_ref.rs b/src/frontend/src/optimizer/plan_node/logical_cte_ref.rs new file mode 100644 index 0000000000000..473ed7d06f67d --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/logical_cte_ref.rs @@ -0,0 +1,98 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_common::bail_not_implemented; +use risingwave_common::util::column_index_mapping::ColIndexMapping; + +use super::expr_visitable::ExprVisitable; +use super::utils::impl_distill_by_unit; +use super::{ + generic, ColPrunable, ColumnPruningContext, ExprRewritable, Logical, LogicalProject, PlanBase, + PredicatePushdown, PredicatePushdownContext, RewriteStreamContext, ToBatch, ToStream, + ToStreamContext, +}; +use crate::binder::ShareId; +use crate::error::Result; +use crate::utils::Condition; +use crate::PlanRef; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LogicalCteRef { + pub base: PlanBase, + core: generic::CteRef, +} + +impl LogicalCteRef { + pub fn new(share_id: ShareId, base_plan: PlanRef) -> Self { + let core = generic::CteRef::new(share_id, base_plan); + let base = PlanBase::new_logical_with_core(&core); + Self { base, core } + } + + pub fn create(share_id: ShareId, base_plan: PlanRef) -> PlanRef { + Self::new(share_id, base_plan).into() + } +} + +impl_plan_tree_node_for_leaf! {LogicalCteRef} + +impl_distill_by_unit! {LogicalCteRef, core, "LogicalCteRef"} + +impl ExprRewritable for LogicalCteRef {} + +impl ExprVisitable for LogicalCteRef {} + +impl ColPrunable for LogicalCteRef { + fn prune_col(&self, required_cols: &[usize], _ctx: &mut ColumnPruningContext) -> PlanRef { + LogicalProject::with_out_col_idx(self.clone().into(), required_cols.iter().copied()).into() + } +} + +impl PredicatePushdown for LogicalCteRef { + fn predicate_pushdown( + &self, + _predicate: Condition, + _ctx: &mut PredicatePushdownContext, + ) -> PlanRef { + self.clone().into() + } +} + +impl ToBatch for LogicalCteRef { + fn to_batch(&self) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_batch of LogicalCteRef" + ) + } +} + +impl ToStream for LogicalCteRef { + fn to_stream(&self, _ctx: &mut ToStreamContext) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_stream of LogicalCteRef" + ) + } + + fn logical_rewrite_for_stream( + &self, + _ctx: &mut RewriteStreamContext, + ) -> Result<(PlanRef, ColIndexMapping)> { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for logical_rewrite_for_stream of LogicalCteRef" + ) + } +} diff --git a/src/frontend/src/optimizer/plan_node/logical_filter.rs b/src/frontend/src/optimizer/plan_node/logical_filter.rs index 6bae6cc7efb22..04cc2cb12a689 100644 --- a/src/frontend/src/optimizer/plan_node/logical_filter.rs +++ b/src/frontend/src/optimizer/plan_node/logical_filter.rs @@ -64,27 +64,17 @@ impl LogicalFilter { } } - /// Create a `LogicalFilter` to filter the rows with all keys are null. - pub fn filter_if_keys_all_null(input: PlanRef, key: &[usize]) -> PlanRef { + /// Create a `LogicalFilter` to filter out rows where all keys are null. + pub fn filter_out_all_null_keys(input: PlanRef, key: &[usize]) -> PlanRef { let schema = input.schema(); - let cond = key.iter().fold(ExprImpl::literal_bool(false), |expr, i| { - ExprImpl::FunctionCall( - FunctionCall::new_unchecked( - ExprType::Or, - vec![ - expr, - FunctionCall::new_unchecked( - ExprType::IsNotNull, - vec![InputRef::new(*i, schema.fields()[*i].data_type.clone()).into()], - DataType::Boolean, - ) - .into(), - ], - DataType::Boolean, - ) - .into(), + let cond = ExprImpl::or(key.iter().unique().map(|&i| { + FunctionCall::new_unchecked( + ExprType::IsNotNull, + vec![InputRef::new(i, schema.fields()[i].data_type.clone()).into()], + DataType::Boolean, ) - }); + .into() + })); LogicalFilter::create_with_expr(input, cond) } @@ -247,11 +237,11 @@ mod tests { use std::collections::HashSet; use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::{DataType, ScalarImpl}; + use risingwave_common::types::ScalarImpl; use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{assert_eq_input_ref, FunctionCall, InputRef, Literal}; + use crate::expr::{assert_eq_input_ref, Literal}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_hop_window.rs b/src/frontend/src/optimizer/plan_node/logical_hop_window.rs index 37f1dba5c2720..b15424d6d892a 100644 --- a/src/frontend/src/optimizer/plan_node/logical_hop_window.rs +++ b/src/frontend/src/optimizer/plan_node/logical_hop_window.rs @@ -366,7 +366,6 @@ mod test { use risingwave_common::types::DataType; use super::*; - use crate::expr::InputRef; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_join.rs b/src/frontend/src/optimizer/plan_node/logical_join.rs index 571efee542c2b..a8a832407ba68 100644 --- a/src/frontend/src/optimizer/plan_node/logical_join.rs +++ b/src/frontend/src/optimizer/plan_node/logical_join.rs @@ -1339,10 +1339,9 @@ impl ToStream for LogicalJoin { } else { Err(RwError::from(ErrorCode::NotSupported( "streaming nested-loop join".to_string(), - // TODO: replace the link with user doc "The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. \ Consider rewriting the query to use dynamic filter as a substitute if possible.\n\ - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md".to_owned(), + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/".to_owned(), ))) } } @@ -1452,7 +1451,7 @@ impl ToStream for LogicalJoin { ) .collect_vec(); let plan: PlanRef = join_with_pk.into(); - LogicalFilter::filter_if_keys_all_null(plan, &left_right_stream_keys) + LogicalFilter::filter_out_all_null_keys(plan, &left_right_stream_keys) } else { join_with_pk.into() }; @@ -1472,7 +1471,7 @@ mod tests { use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{assert_eq_input_ref, FunctionCall, InputRef, Literal}; + use crate::expr::{assert_eq_input_ref, FunctionCall, Literal}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_multi_join.rs b/src/frontend/src/optimizer/plan_node/logical_multi_join.rs index a47c75d31bd27..a5ae569456336 100644 --- a/src/frontend/src/optimizer/plan_node/logical_multi_join.rs +++ b/src/frontend/src/optimizer/plan_node/logical_multi_join.rs @@ -406,7 +406,7 @@ impl LogicalMultiJoin { /// a. a filter with the non eq conditions /// b. a projection which reorders the output column ordering to agree with the /// original ordering of the joins. - /// The filter will then be pushed down by another filter pushdown pass. + /// The filter will then be pushed down by another filter pushdown pass. pub(crate) fn heuristic_ordering(&self) -> Result> { let mut labeller = ConnectedComponentLabeller::new(self.inputs.len()); @@ -494,9 +494,9 @@ impl LogicalMultiJoin { /// 2. Second, for every isolated node will create connection to every other nodes. /// 3. Third, select and merge one node for a iteration, and use a bfs policy for which node the /// selected node merged with. - /// i. The select node mentioned above is the node with least number of relations and the + /// i. The select node mentioned above is the node with least number of relations and the /// lowerst join tree. - /// ii. nodes with a join tree higher than the temporal optimal join tree will be pruned. + /// ii. nodes with a join tree higher than the temporal optimal join tree will be pruned. pub fn as_bushy_tree_join(&self) -> Result { let (nodes, condition) = self.get_join_graph()?; @@ -871,7 +871,7 @@ mod test { use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{FunctionCall, InputRef}; + use crate::expr::InputRef; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::generic::GenericPlanRef; use crate::optimizer::plan_node::LogicalValues; diff --git a/src/frontend/src/optimizer/plan_node/logical_now.rs b/src/frontend/src/optimizer/plan_node/logical_now.rs index f9c33eb3d9cc1..c96bfd6197fda 100644 --- a/src/frontend/src/optimizer/plan_node/logical_now.rs +++ b/src/frontend/src/optimizer/plan_node/logical_now.rs @@ -14,42 +14,37 @@ use pretty_xmlish::XmlNode; use risingwave_common::bail; -use risingwave_common::catalog::{Field, Schema}; -use risingwave_common::types::DataType; +use risingwave_common::catalog::Schema; -use super::generic::GenericPlanRef; +use super::generic::{self, GenericPlanRef, Mode}; use super::utils::{childless_record, Distill}; use super::{ - ColPrunable, ColumnPruningContext, ExprRewritable, Logical, LogicalFilter, PlanBase, PlanRef, - PredicatePushdown, RewriteStreamContext, StreamNow, ToBatch, ToStream, ToStreamContext, + ColPrunable, ColumnPruningContext, ExprRewritable, Logical, LogicalFilter, LogicalValues, + PlanBase, PlanRef, PredicatePushdown, RewriteStreamContext, StreamNow, ToBatch, ToStream, + ToStreamContext, }; use crate::error::Result; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; use crate::optimizer::plan_node::utils::column_names_pretty; -use crate::optimizer::property::FunctionalDependencySet; use crate::utils::ColIndexMapping; -use crate::OptimizerContextRef; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct LogicalNow { pub base: PlanBase, + core: generic::Now, } impl LogicalNow { - pub fn new(ctx: OptimizerContextRef) -> Self { - let schema = Schema::new(vec![Field { - data_type: DataType::Timestamptz, - name: String::from("now"), - sub_fields: vec![], - type_name: String::default(), - }]); - let base = PlanBase::new_logical( - ctx, - schema, - Some(vec![]), - FunctionalDependencySet::default(), - ); - Self { base } + pub fn new(core: generic::Now) -> Self { + let base = PlanBase::new_logical_with_core(&core); + Self { base, core } + } + + pub fn max_one_row(&self) -> bool { + match self.core.mode { + Mode::UpdateCurrent => true, + Mode::GenerateSeries { .. } => false, + } } } @@ -91,7 +86,7 @@ impl ToStream for LogicalNow { /// `to_stream` is equivalent to `to_stream_with_dist_required(RequiredDist::Any)` fn to_stream(&self, _ctx: &mut ToStreamContext) -> Result { - Ok(StreamNow::new(self.clone(), self.ctx()).into()) + Ok(StreamNow::new(self.core.clone()).into()) } } @@ -103,7 +98,12 @@ impl ToBatch for LogicalNow { /// The trait for column pruning, only logical plan node will use it, though all plan node impl it. impl ColPrunable for LogicalNow { - fn prune_col(&self, _required_cols: &[usize], _ctx: &mut ColumnPruningContext) -> PlanRef { - self.clone().into() + fn prune_col(&self, required_cols: &[usize], _: &mut ColumnPruningContext) -> PlanRef { + if required_cols.is_empty() { + LogicalValues::new(vec![], Schema::empty().clone(), self.ctx()).into() + } else { + assert_eq!(required_cols, &[0], "we only output one column"); + self.clone().into() + } } } diff --git a/src/frontend/src/optimizer/plan_node/logical_over_window.rs b/src/frontend/src/optimizer/plan_node/logical_over_window.rs index af1fc50c9057f..bd38569397992 100644 --- a/src/frontend/src/optimizer/plan_node/logical_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/logical_over_window.rs @@ -14,7 +14,7 @@ use fixedbitset::FixedBitSet; use itertools::Itertools; -use risingwave_common::types::{DataType, Datum, ScalarImpl}; +use risingwave_common::types::DataType; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_common::{bail_not_implemented, not_implemented}; use risingwave_expr::aggregate::AggKind; @@ -29,9 +29,11 @@ use super::{ }; use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{ - Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, FunctionCall, InputRef, WindowFunction, + AggCall, Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, FunctionCall, InputRef, + WindowFunction, }; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; +use crate::optimizer::plan_node::logical_agg::LogicalAggBuilder; use crate::optimizer::plan_node::{ ColumnPruningContext, Literal, PredicatePushdownContext, RewriteStreamContext, ToStreamContext, }; @@ -103,7 +105,7 @@ impl<'a> LogicalOverWindowBuilder<'a> { window_func.frame, ); - if let WindowFuncKind::Aggregate(agg_kind) = kind + let new_expr = if let WindowFuncKind::Aggregate(agg_kind) = kind && matches!( agg_kind, AggKind::Avg @@ -111,148 +113,39 @@ impl<'a> LogicalOverWindowBuilder<'a> { | AggKind::StddevSamp | AggKind::VarPop | AggKind::VarSamp - ) - { - // Refer to `LogicalAggBuilder::try_rewrite_agg_call` - match agg_kind { - AggKind::Avg => { - assert_eq!(args.len(), 1); - let left_ref = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Sum), - partition_by.clone(), - order_by.clone(), - args.clone(), - frame.clone(), - )?)) - .cast_explicit(return_type)?; - let right_ref = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Count), - partition_by, - order_by, - args, - frame, - )?)); - - let new_expr = ExprImpl::from(FunctionCall::new( - ExprType::Divide, - vec![left_ref, right_ref], - )?); - Ok(new_expr) - } - AggKind::StddevPop | AggKind::StddevSamp | AggKind::VarPop | AggKind::VarSamp => { - let input = args.first().unwrap(); - let squared_input_expr = ExprImpl::from(FunctionCall::new( - ExprType::Multiply, - vec![input.clone(), input.clone()], - )?); - - let sum_of_squares_expr = - ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Sum), - partition_by.clone(), - order_by.clone(), - vec![squared_input_expr], - frame.clone(), - )?)) - .cast_explicit(return_type.clone())?; - - let sum_expr = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Sum), + ) { + let agg_call = AggCall::new( + agg_kind, + args, + false, + order_by, + Condition::true_cond(), + vec![], + )?; + LogicalAggBuilder::general_rewrite_agg_call(agg_call, |agg_call| { + Ok(self.push_window_func( + // AggCall -> WindowFunction + WindowFunction::new( + WindowFuncKind::Aggregate(agg_call.agg_kind), partition_by.clone(), - order_by.clone(), - args.clone(), + agg_call.order_by.clone(), + agg_call.args.clone(), frame.clone(), - )?)) - .cast_explicit(return_type.clone())?; - - let count_expr = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Count), - partition_by, - order_by, - args.clone(), - frame, - )?)); - - let square_of_sum_expr = ExprImpl::from(FunctionCall::new( - ExprType::Multiply, - vec![sum_expr.clone(), sum_expr], - )?); - - let numerator_expr = ExprImpl::from(FunctionCall::new( - ExprType::Subtract, - vec![ - sum_of_squares_expr, - ExprImpl::from(FunctionCall::new( - ExprType::Divide, - vec![square_of_sum_expr, count_expr.clone()], - )?), - ], - )?); - - let denominator_expr = match agg_kind { - AggKind::StddevPop | AggKind::VarPop => count_expr.clone(), - AggKind::StddevSamp | AggKind::VarSamp => { - ExprImpl::from(FunctionCall::new( - ExprType::Subtract, - vec![ - count_expr.clone(), - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - )?) - } - _ => unreachable!(), - }; - - let mut target_expr = ExprImpl::from(FunctionCall::new( - ExprType::Divide, - vec![numerator_expr, denominator_expr], - )?); - - if matches!(agg_kind, AggKind::StddevPop | AggKind::StddevSamp) { - target_expr = ExprImpl::from( - FunctionCall::new(ExprType::Sqrt, vec![target_expr]).unwrap(), - ); - } - - match agg_kind { - AggKind::VarPop | AggKind::StddevPop => Ok(target_expr), - AggKind::StddevSamp | AggKind::VarSamp => { - let less_than_expr = ExprImpl::from(FunctionCall::new( - ExprType::LessThanOrEqual, - vec![ - count_expr, - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - )?); - let null_expr = ExprImpl::from(Literal::new(None, return_type)); - - let case_expr = ExprImpl::from(FunctionCall::new( - ExprType::Case, - vec![less_than_expr, null_expr, target_expr], - )?); - Ok(case_expr) - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } + )?, + )) + })? } else { - let new_expr = ExprImpl::from(self.push_window_func(WindowFunction::new( + ExprImpl::from(self.push_window_func(WindowFunction::new( kind, partition_by, order_by, args, frame, - )?)); - Ok(new_expr) - } + )?)) + }; + + assert_eq!(new_expr.return_type(), return_type); + Ok(new_expr) } } @@ -711,6 +604,16 @@ impl PredicatePushdown for LogicalOverWindow { } } +macro_rules! empty_partition_by_not_implemented { + () => { + bail_not_implemented!( + issue = 11505, + "Window function with empty PARTITION BY is not supported because of potential bad performance. \ + If you really need this, please workaround with something like `PARTITION BY 1::int`." + ) + }; +} + impl ToBatch for LogicalOverWindow { fn to_batch(&self) -> Result { assert!( @@ -726,7 +629,7 @@ impl ToBatch for LogicalOverWindow { .map(|e| e.index()) .collect_vec(); if partition_key_indices.is_empty() { - bail_not_implemented!("Window function with empty PARTITION BY is not supported yet"); + empty_partition_by_not_implemented!(); } let input = self.input().to_batch()?; @@ -777,9 +680,7 @@ impl ToStream for LogicalOverWindow { .map(|e| e.index()) .collect_vec(); if partition_key_indices.is_empty() { - bail_not_implemented!( - "Window function with empty PARTITION BY is not supported yet" - ); + empty_partition_by_not_implemented!(); } let sort_input = @@ -793,6 +694,17 @@ impl ToStream for LogicalOverWindow { } else { // General (Emit-On-Update) case + if self + .window_functions() + .iter() + .any(|f| f.frame.bounds.is_session()) + { + bail_not_implemented!( + "Session frame is not yet supported in general streaming mode. \ + Please consider using Emit-On-Window-Close mode." + ); + } + // TODO(rc): Let's not introduce too many cases at once. Later we may decide to support // empty PARTITION BY by simply removing the following check. let partition_key_indices = self.window_functions()[0] @@ -801,9 +713,7 @@ impl ToStream for LogicalOverWindow { .map(|e| e.index()) .collect_vec(); if partition_key_indices.is_empty() { - bail_not_implemented!( - "Window function with empty PARTITION BY is not supported yet" - ); + empty_partition_by_not_implemented!(); } let new_input = diff --git a/src/frontend/src/optimizer/plan_node/logical_project.rs b/src/frontend/src/optimizer/plan_node/logical_project.rs index a0f26548da2b1..78bd74969b2c1 100644 --- a/src/frontend/src/optimizer/plan_node/logical_project.rs +++ b/src/frontend/src/optimizer/plan_node/logical_project.rs @@ -303,7 +303,7 @@ mod tests { use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{assert_eq_input_ref, FunctionCall, InputRef, Literal}; + use crate::expr::{assert_eq_input_ref, FunctionCall, Literal}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; diff --git a/src/frontend/src/optimizer/plan_node/logical_project_set.rs b/src/frontend/src/optimizer/plan_node/logical_project_set.rs index 53e7995c1517e..8f6966ece6c70 100644 --- a/src/frontend/src/optimizer/plan_node/logical_project_set.rs +++ b/src/frontend/src/optimizer/plan_node/logical_project_set.rs @@ -22,7 +22,7 @@ use super::{ LogicalProject, PlanBase, PlanRef, PlanTreeNodeUnary, PredicatePushdown, StreamProjectSet, ToBatch, ToStream, }; -use crate::error::Result; +use crate::error::{ErrorCode, Result}; use crate::expr::{ collect_input_refs, Expr, ExprImpl, ExprRewriter, ExprVisitor, FunctionCall, InputRef, TableFunction, @@ -104,7 +104,7 @@ impl LogicalProjectSet { args, return_type, function_type, - udtf_catalog, + user_defined, } = table_func; let args = args .into_iter() @@ -116,7 +116,7 @@ impl LogicalProjectSet { args, return_type, function_type, - udtf_catalog, + user_defined, } .into() } else { @@ -400,6 +400,16 @@ impl ToStream for LogicalProjectSet { // TODO: implement to_stream_with_dist_required like LogicalProject fn to_stream(&self, ctx: &mut ToStreamContext) -> Result { + if self.select_list().iter().any(|item| item.has_now()) { + // User may use `now()` in table function in a wrong way, because we allow `now()` in `FROM` clause. + return Err(ErrorCode::NotSupported( + "General `now()` function in streaming queries".to_string(), + "Streaming `now()` is currently only supported in GenerateSeries and TemporalFilter patterns." + .to_string(), + ) + .into()); + } + let new_input = self.input().to_stream(ctx)?; let mut new_logical = self.core.clone(); new_logical.input = new_input; @@ -412,10 +422,8 @@ mod test { use std::collections::HashSet; use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::DataType; use super::*; - use crate::expr::{ExprImpl, InputRef, TableFunction}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_recursive_union.rs b/src/frontend/src/optimizer/plan_node/logical_recursive_union.rs new file mode 100644 index 0000000000000..04a3dc297d352 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/logical_recursive_union.rs @@ -0,0 +1,141 @@ +// Copyright 2024 RisingWave Labs +// +// 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 itertools::Itertools; +use pretty_xmlish::{Pretty, XmlNode}; +use risingwave_common::bail_not_implemented; +use risingwave_common::util::column_index_mapping::ColIndexMapping; +use smallvec::{smallvec, SmallVec}; + +use super::expr_visitable::ExprVisitable; +use super::generic::GenericPlanRef; +use super::utils::{childless_record, Distill}; +use super::{ + generic, ColPrunable, ColumnPruningContext, ExprRewritable, Logical, PlanBase, PlanTreeNode, + PredicatePushdown, PredicatePushdownContext, RewriteStreamContext, ToBatch, ToStream, + ToStreamContext, +}; +use crate::binder::ShareId; +use crate::error::Result; +use crate::utils::Condition; +use crate::PlanRef; + +/// `LogicalRecursiveUnion` returns the union of the rows of its inputs. +/// note: if `all` is false, it needs to eliminate duplicates. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LogicalRecursiveUnion { + pub base: PlanBase, + core: generic::RecursiveUnion, +} + +impl LogicalRecursiveUnion { + pub fn new(base_plan: PlanRef, recursive: PlanRef, id: ShareId) -> Self { + let core = generic::RecursiveUnion { + base: base_plan, + recursive, + id, + }; + let base = PlanBase::new_logical_with_core(&core); + LogicalRecursiveUnion { base, core } + } + + pub fn create(base_plan: PlanRef, recursive: PlanRef, id: ShareId) -> PlanRef { + Self::new(base_plan, recursive, id).into() + } + + pub(super) fn pretty_fields(base: impl GenericPlanRef, name: &str) -> XmlNode<'_> { + childless_record(name, vec![("id", Pretty::debug(&base.id().0))]) + } +} + +impl PlanTreeNode for LogicalRecursiveUnion { + fn inputs(&self) -> SmallVec<[PlanRef; 2]> { + smallvec![self.core.base.clone(), self.core.recursive.clone()] + } + + fn clone_with_inputs(&self, inputs: &[PlanRef]) -> PlanRef { + let mut inputs = inputs.iter().cloned(); + Self::create(inputs.next().unwrap(), inputs.next().unwrap(), self.core.id) + } +} + +impl Distill for LogicalRecursiveUnion { + fn distill<'a>(&self) -> XmlNode<'a> { + Self::pretty_fields(&self.base, "LogicalRecursiveUnion") + } +} + +impl ColPrunable for LogicalRecursiveUnion { + fn prune_col(&self, required_cols: &[usize], ctx: &mut ColumnPruningContext) -> PlanRef { + let new_inputs = self + .inputs() + .iter() + .map(|input| input.prune_col(required_cols, ctx)) + .collect_vec(); + let new_plan = self.clone_with_inputs(&new_inputs); + self.ctx() + .insert_rcte_cache_plan(self.core.id, new_plan.clone()); + new_plan + } +} + +impl ExprRewritable for LogicalRecursiveUnion {} + +impl ExprVisitable for LogicalRecursiveUnion {} + +impl PredicatePushdown for LogicalRecursiveUnion { + fn predicate_pushdown( + &self, + predicate: Condition, + ctx: &mut PredicatePushdownContext, + ) -> PlanRef { + let new_inputs = self + .inputs() + .iter() + .map(|input| input.predicate_pushdown(predicate.clone(), ctx)) + .collect_vec(); + let new_plan = self.clone_with_inputs(&new_inputs); + self.ctx() + .insert_rcte_cache_plan(self.core.id, new_plan.clone()); + new_plan + } +} + +impl ToBatch for LogicalRecursiveUnion { + fn to_batch(&self) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_batch of LogicalRecursiveUnion" + ) + } +} + +impl ToStream for LogicalRecursiveUnion { + fn to_stream(&self, _ctx: &mut ToStreamContext) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_stream of LogicalRecursiveUnion" + ) + } + + fn logical_rewrite_for_stream( + &self, + _ctx: &mut RewriteStreamContext, + ) -> Result<(PlanRef, ColIndexMapping)> { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for logical_rewrite_for_stream of LogicalRecursiveUnion" + ) + } +} diff --git a/src/frontend/src/optimizer/plan_node/logical_scan.rs b/src/frontend/src/optimizer/plan_node/logical_scan.rs index 77f26d0adc45e..e2aeaa6b9517a 100644 --- a/src/frontend/src/optimizer/plan_node/logical_scan.rs +++ b/src/frontend/src/optimizer/plan_node/logical_scan.rs @@ -164,16 +164,14 @@ impl LogicalScan { .pk() .iter() .map(|idx_item| { - ColumnOrder::new( - *output_col_map - .get( - s2p_mapping - .get(&idx_item.column_index) - .expect("should be in s2p mapping"), - ) - .unwrap_or(&unmatched_idx), - idx_item.order_type, - ) + let idx = match s2p_mapping.get(&idx_item.column_index) { + Some(col_idx) => { + *output_col_map.get(col_idx).unwrap_or(&unmatched_idx) + } + // After we support index on expressions, we need to handle the case where the column is not in the `s2p_mapping`. + None => unmatched_idx, + }; + ColumnOrder::new(idx, idx_item.order_type) }) .collect(), } diff --git a/src/frontend/src/optimizer/plan_node/logical_source.rs b/src/frontend/src/optimizer/plan_node/logical_source.rs index 50a28494e81df..918db2919e626 100644 --- a/src/frontend/src/optimizer/plan_node/logical_source.rs +++ b/src/frontend/src/optimizer/plan_node/logical_source.rs @@ -70,6 +70,12 @@ impl LogicalSource { ctx: OptimizerContextRef, as_of: Option, ) -> Result { + // XXX: should we reorder the columns? + // The order may be strange if the schema is changed, e.g., [foo:Varchar, _rw_kafka_timestamp:Timestamptz, _row_id:Serial, bar:Int32] + // related: https://github.com/risingwavelabs/risingwave/issues/16486 + // The order does not matter much. The columns field is essentially a map indexed by the column id. + // It will affect what users will see in `SELECT *`. + // But not sure if we rely on the position of hidden column like `_row_id` somewhere. For `projected_row_id` we do so... let core = generic::Source { catalog: source_catalog, column_catalog, @@ -347,8 +353,9 @@ impl ToStream for LogicalSource { } SourceNodeKind::CreateMViewOrBatch => { // Create MV on source. - let use_shared_source = self.source_catalog().is_some_and(|c| c.info.is_shared()) - && self.ctx().session_ctx().config().rw_enable_shared_source(); + // We only check rw_enable_shared_source is true when `CREATE SOURCE`. + // The value does not affect the behavior of `CREATE MATERIALIZED VIEW` here. + let use_shared_source = self.source_catalog().is_some_and(|c| c.info.is_shared()); if use_shared_source { plan = StreamSourceScan::new(self.core.clone()).into(); } else { diff --git a/src/frontend/src/optimizer/plan_node/logical_union.rs b/src/frontend/src/optimizer/plan_node/logical_union.rs index 99de2748dca4e..6238e63fd9f51 100644 --- a/src/frontend/src/optimizer/plan_node/logical_union.rs +++ b/src/frontend/src/optimizer/plan_node/logical_union.rs @@ -327,8 +327,7 @@ impl ToStream for LogicalUnion { #[cfg(test)] mod tests { - use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::DataType; + use risingwave_common::catalog::Field; use super::*; use crate::optimizer::optimizer_context::OptimizerContext; diff --git a/src/frontend/src/optimizer/plan_node/logical_values.rs b/src/frontend/src/optimizer/plan_node/logical_values.rs index cdf9af3c93541..7a585f7e0801f 100644 --- a/src/frontend/src/optimizer/plan_node/logical_values.rs +++ b/src/frontend/src/optimizer/plan_node/logical_values.rs @@ -205,11 +205,9 @@ impl ToStream for LogicalValues { #[cfg(test)] mod tests { - use risingwave_common::catalog::Field; - use risingwave_common::types::{DataType, Datum}; + use risingwave_common::types::Datum; use super::*; - use crate::expr::Literal; use crate::optimizer::optimizer_context::OptimizerContext; fn literal(val: i32) -> ExprImpl { diff --git a/src/frontend/src/optimizer/plan_node/mod.rs b/src/frontend/src/optimizer/plan_node/mod.rs index e7ad78f373bac..71c4c44fac8ba 100644 --- a/src/frontend/src/optimizer/plan_node/mod.rs +++ b/src/frontend/src/optimizer/plan_node/mod.rs @@ -33,15 +33,15 @@ use std::ops::Deref; use std::rc::Rc; use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{self, DynClone}; +use dyn_clone::DynClone; use fixedbitset::FixedBitSet; use itertools::Itertools; use paste::paste; use pretty_xmlish::{Pretty, PrettyConfig}; use risingwave_common::catalog::Schema; use risingwave_common::util::recursive::{self, Recurse}; -use risingwave_pb::batch_plan::PlanNode as BatchPlanPb; -use risingwave_pb::stream_plan::StreamNode as StreamPlanPb; +use risingwave_pb::batch_plan::PlanNode as PbBatchPlan; +use risingwave_pb::stream_plan::StreamNode as PbStreamPlan; use serde::Serialize; use smallvec::SmallVec; @@ -696,8 +696,10 @@ impl dyn PlanNode { } } -const PLAN_DEPTH_THRESHOLD: usize = 30; -const PLAN_TOO_DEEP_NOTICE: &str = "The plan is too deep. \ +/// Recursion depth threshold for plan node visitor to send notice to user. +pub const PLAN_DEPTH_THRESHOLD: usize = 30; +/// Notice message for plan node visitor to send to user when the depth threshold is reached. +pub const PLAN_TOO_DEEP_NOTICE: &str = "The plan is too deep. \ Consider simplifying or splitting the query if you encounter any issues."; impl dyn PlanNode { @@ -708,7 +710,7 @@ impl dyn PlanNode { pub fn to_stream_prost( &self, state: &mut BuildFragmentGraphState, - ) -> SchedulerResult { + ) -> SchedulerResult { recursive::tracker!().recurse(|t| { if t.depth_reaches(PLAN_DEPTH_THRESHOLD) { notice_to_user(PLAN_TOO_DEEP_NOTICE); @@ -736,7 +738,7 @@ impl dyn PlanNode { .map(|plan| plan.to_stream_prost(state)) .try_collect()?; // TODO: support pk_indices and operator_id - Ok(StreamPlanPb { + Ok(PbStreamPlan { input, identity: self.explain_myself_to_string(), node_body: node, @@ -754,13 +756,13 @@ impl dyn PlanNode { } /// Serialize the plan node and its children to a batch plan proto. - pub fn to_batch_prost(&self) -> SchedulerResult { + pub fn to_batch_prost(&self) -> SchedulerResult { self.to_batch_prost_identity(true) } /// Serialize the plan node and its children to a batch plan proto without the identity field /// (for testing). - pub fn to_batch_prost_identity(&self, identity: bool) -> SchedulerResult { + pub fn to_batch_prost_identity(&self, identity: bool) -> SchedulerResult { recursive::tracker!().recurse(|t| { if t.depth_reaches(PLAN_DEPTH_THRESHOLD) { notice_to_user(PLAN_TOO_DEEP_NOTICE); @@ -772,7 +774,7 @@ impl dyn PlanNode { .into_iter() .map(|plan| plan.to_batch_prost_identity(identity)) .try_collect()?; - Ok(BatchPlanPb { + Ok(PbBatchPlan { children, identity: if identity { self.explain_myself_to_string() @@ -827,6 +829,7 @@ mod batch_hash_join; mod batch_hop_window; mod batch_insert; mod batch_limit; +mod batch_log_seq_scan; mod batch_lookup_join; mod batch_max_one_row; mod batch_nested_loop_join; @@ -847,6 +850,8 @@ mod batch_values; mod logical_agg; mod logical_apply; mod logical_cdc_scan; +mod logical_changelog; +mod logical_cte_ref; mod logical_dedup; mod logical_delete; mod logical_except; @@ -864,6 +869,7 @@ mod logical_now; mod logical_over_window; mod logical_project; mod logical_project_set; +mod logical_recursive_union; mod logical_scan; mod logical_share; mod logical_source; @@ -873,6 +879,7 @@ mod logical_topn; mod logical_union; mod logical_update; mod logical_values; +mod stream_changelog; mod stream_dedup; mod stream_delta_join; mod stream_dml; @@ -898,7 +905,6 @@ mod stream_sort; mod stream_source; mod stream_source_scan; mod stream_stateless_simple_agg; -mod stream_subscription; mod stream_table_scan; mod stream_topn; mod stream_values; @@ -926,6 +932,7 @@ pub use batch_iceberg_scan::BatchIcebergScan; pub use batch_insert::BatchInsert; pub use batch_kafka_scan::BatchKafkaScan; pub use batch_limit::BatchLimit; +pub use batch_log_seq_scan::BatchLogSeqScan; pub use batch_lookup_join::BatchLookupJoin; pub use batch_max_one_row::BatchMaxOneRow; pub use batch_nested_loop_join::BatchNestedLoopJoin; @@ -946,6 +953,8 @@ pub use batch_values::BatchValues; pub use logical_agg::LogicalAgg; pub use logical_apply::LogicalApply; pub use logical_cdc_scan::LogicalCdcScan; +pub use logical_changelog::LogicalChangeLog; +pub use logical_cte_ref::LogicalCteRef; pub use logical_dedup::LogicalDedup; pub use logical_delete::LogicalDelete; pub use logical_except::LogicalExcept; @@ -964,6 +973,7 @@ pub use logical_now::LogicalNow; pub use logical_over_window::LogicalOverWindow; pub use logical_project::LogicalProject; pub use logical_project_set::LogicalProjectSet; +pub use logical_recursive_union::LogicalRecursiveUnion; pub use logical_scan::LogicalScan; pub use logical_share::LogicalShare; pub use logical_source::LogicalSource; @@ -974,6 +984,7 @@ pub use logical_union::LogicalUnion; pub use logical_update::LogicalUpdate; pub use logical_values::LogicalValues; pub use stream_cdc_table_scan::StreamCdcTableScan; +pub use stream_changelog::StreamChangeLog; pub use stream_dedup::StreamDedup; pub use stream_delta_join::StreamDeltaJoin; pub use stream_dml::StreamDml; @@ -1000,7 +1011,6 @@ pub use stream_sort::StreamEowcSort; pub use stream_source::StreamSource; pub use stream_source_scan::StreamSourceScan; pub use stream_stateless_simple_agg::StreamStatelessSimpleAgg; -pub use stream_subscription::StreamSubscription; pub use stream_table_scan::StreamTableScan; pub use stream_temporal_join::StreamTemporalJoin; pub use stream_topn::StreamTopN; @@ -1063,6 +1073,9 @@ macro_rules! for_all_plan_nodes { , { Logical, MaxOneRow } , { Logical, KafkaScan } , { Logical, IcebergScan } + , { Logical, RecursiveUnion } + , { Logical, CteRef } + , { Logical, ChangeLog } , { Batch, SimpleAgg } , { Batch, HashAgg } , { Batch, SortAgg } @@ -1073,6 +1086,7 @@ macro_rules! for_all_plan_nodes { , { Batch, Update } , { Batch, SeqScan } , { Batch, SysSeqScan } + , { Batch, LogSeqScan } , { Batch, HashJoin } , { Batch, NestedLoopJoin } , { Batch, Values } @@ -1097,7 +1111,6 @@ macro_rules! for_all_plan_nodes { , { Stream, TableScan } , { Stream, CdcTableScan } , { Stream, Sink } - , { Stream, Subscription } , { Stream, Source } , { Stream, SourceScan } , { Stream, HashJoin } @@ -1126,6 +1139,7 @@ macro_rules! for_all_plan_nodes { , { Stream, EowcSort } , { Stream, OverWindow } , { Stream, FsFetch } + , { Stream, ChangeLog } } }; } @@ -1165,6 +1179,9 @@ macro_rules! for_logical_plan_nodes { , { Logical, MaxOneRow } , { Logical, KafkaScan } , { Logical, IcebergScan } + , { Logical, RecursiveUnion } + , { Logical, CteRef } + , { Logical, ChangeLog } } }; } @@ -1181,6 +1198,7 @@ macro_rules! for_batch_plan_nodes { , { Batch, Filter } , { Batch, SeqScan } , { Batch, SysSeqScan } + , { Batch, LogSeqScan } , { Batch, HashJoin } , { Batch, NestedLoopJoin } , { Batch, Values } @@ -1219,7 +1237,6 @@ macro_rules! for_stream_plan_nodes { , { Stream, TableScan } , { Stream, CdcTableScan } , { Stream, Sink } - , { Stream, Subscription } , { Stream, Source } , { Stream, SourceScan } , { Stream, HashAgg } @@ -1246,6 +1263,7 @@ macro_rules! for_stream_plan_nodes { , { Stream, EowcSort } , { Stream, OverWindow } , { Stream, FsFetch } + , { Stream, ChangeLog } } }; } diff --git a/src/frontend/src/optimizer/plan_node/plan_base.rs b/src/frontend/src/optimizer/plan_node/plan_base.rs index 3e9783894d40f..12fba475241c5 100644 --- a/src/frontend/src/optimizer/plan_node/plan_base.rs +++ b/src/frontend/src/optimizer/plan_node/plan_base.rs @@ -13,13 +13,10 @@ // limitations under the License. use educe::Educe; -use fixedbitset::FixedBitSet; -use risingwave_common::catalog::Schema; use super::generic::GenericPlanNode; use super::*; -use crate::optimizer::optimizer_context::OptimizerContextRef; -use crate::optimizer::property::{Distribution, FunctionalDependencySet, Order}; +use crate::optimizer::property::Distribution; /// No extra fields for logical plan nodes. #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs b/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs index 64abc6bcd2de8..b7c429f4f6399 100644 --- a/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs +++ b/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs @@ -19,7 +19,6 @@ use paste::paste; use super::*; use crate::optimizer::plan_visitor::ShareParentCounter; use crate::optimizer::PlanVisitor; -use crate::utils::Condition; use crate::{for_batch_plan_nodes, for_stream_plan_nodes}; /// The trait for predicate pushdown, only logical plan node will use it, though all plan node impl @@ -30,14 +29,14 @@ pub trait PredicatePushdown { /// There are three kinds of predicates: /// /// 1. those can't be pushed down. We just create a `LogicalFilter` for them above the current - /// `PlanNode`. i.e., + /// `PlanNode`. i.e., /// /// ```ignore /// LogicalFilter::create(self.clone().into(), predicate) /// ``` /// /// 2. those can be merged with current `PlanNode` (e.g., `LogicalJoin`). We just merge - /// the predicates with the `Condition` of it. + /// the predicates with the `Condition` of it. /// /// 3. those can be pushed down. We pass them to current `PlanNode`'s input. fn predicate_pushdown( diff --git a/src/frontend/src/optimizer/plan_node/stream_cdc_table_scan.rs b/src/frontend/src/optimizer/plan_node/stream_cdc_table_scan.rs index 9df1a94879b22..9fe3347171453 100644 --- a/src/frontend/src/optimizer/plan_node/stream_cdc_table_scan.rs +++ b/src/frontend/src/optimizer/plan_node/stream_cdc_table_scan.rs @@ -247,6 +247,7 @@ impl StreamCdcTableScan { "stream cdc table scan output indices" ); + let options = self.core.options.to_proto(); let stream_scan_body = PbNodeBody::StreamCdcScan(StreamCdcScanNode { table_id: upstream_source_id, upstream_column_ids, @@ -255,7 +256,8 @@ impl StreamCdcTableScan { state_table: Some(catalog), cdc_table_desc: Some(self.core.cdc_table_desc.to_protobuf()), rate_limit: self.base.ctx().overwrite_options().streaming_rate_limit, - disable_backfill: self.core.disable_backfill, + disable_backfill: options.disable_backfill, + options: Some(options), }); // plan: merge -> filter -> exchange(simple) -> stream_scan diff --git a/src/frontend/src/optimizer/plan_node/stream_changelog.rs b/src/frontend/src/optimizer/plan_node/stream_changelog.rs new file mode 100644 index 0000000000000..0ee696c58067a --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/stream_changelog.rs @@ -0,0 +1,80 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_pb::stream_plan::stream_node::PbNodeBody; +use risingwave_pb::stream_plan::ChangeLogNode; + +use super::expr_visitable::ExprVisitable; +use super::stream::prelude::PhysicalPlanRef; +use super::stream::StreamPlanRef; +use super::utils::impl_distill_by_unit; +use super::{generic, ExprRewritable, PlanBase, PlanTreeNodeUnary, Stream, StreamNode}; +use crate::stream_fragmenter::BuildFragmentGraphState; +use crate::PlanRef; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StreamChangeLog { + pub base: PlanBase, + core: generic::ChangeLog, +} + +impl StreamChangeLog { + pub fn new(core: generic::ChangeLog) -> Self { + let input = core.input.clone(); + let dist = input.distribution().clone(); + // Filter executor won't change the append-only behavior of the stream. + let mut watermark_columns = input.watermark_columns().clone(); + if core.need_op { + watermark_columns.grow(input.watermark_columns().len() + 2); + } else { + watermark_columns.grow(input.watermark_columns().len() + 1); + } + let base = PlanBase::new_stream_with_core( + &core, + dist, + // The changelog will convert all delete/update to insert, so it must be true here. + true, + input.emit_on_window_close(), + watermark_columns, + ); + StreamChangeLog { base, core } + } +} + +impl PlanTreeNodeUnary for StreamChangeLog { + fn input(&self) -> PlanRef { + self.core.input.clone() + } + + fn clone_with_input(&self, input: PlanRef) -> Self { + let mut core = self.core.clone(); + core.input = input; + Self::new(core) + } +} + +impl_plan_tree_node_for_unary! { StreamChangeLog } +impl_distill_by_unit!(StreamChangeLog, core, "StreamChangeLog"); + +impl StreamNode for StreamChangeLog { + fn to_stream_prost_body(&self, _state: &mut BuildFragmentGraphState) -> PbNodeBody { + PbNodeBody::Changelog(ChangeLogNode { + need_op: self.core.need_op, + }) + } +} + +impl ExprRewritable for StreamChangeLog {} + +impl ExprVisitable for StreamChangeLog {} diff --git a/src/frontend/src/optimizer/plan_node/stream_delta_join.rs b/src/frontend/src/optimizer/plan_node/stream_delta_join.rs index 89b1402cba919..49257676bc004 100644 --- a/src/frontend/src/optimizer/plan_node/stream_delta_join.rs +++ b/src/frontend/src/optimizer/plan_node/stream_delta_join.rs @@ -20,10 +20,9 @@ use risingwave_pb::plan_common::JoinType; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::{ArrangementInfo, DeltaIndexJoinNode}; -use super::generic::{self, GenericPlanRef}; use super::stream::prelude::*; use super::utils::{childless_record, Distill}; -use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary}; +use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary}; use crate::expr::{Expr, ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; use crate::optimizer::plan_node::utils::IndicesDisplay; diff --git a/src/frontend/src/optimizer/plan_node/stream_dml.rs b/src/frontend/src/optimizer/plan_node/stream_dml.rs index bdda75d061012..e777041718291 100644 --- a/src/frontend/src/optimizer/plan_node/stream_dml.rs +++ b/src/frontend/src/optimizer/plan_node/stream_dml.rs @@ -18,7 +18,6 @@ use risingwave_common::catalog::{ColumnDesc, INITIAL_TABLE_VERSION_ID}; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, Distill}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs b/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs index 974c6fca5df1a..a90cd4b77c669 100644 --- a/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs +++ b/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs @@ -17,9 +17,8 @@ pub use risingwave_pb::expr::expr_node::Type as ExprType; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::DynamicFilterNode; -use super::generic::{DynamicFilter, GenericPlanRef}; +use super::generic::DynamicFilter; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{ childless_record, column_names_pretty, plan_node_name, watermark_pretty, Distill, }; diff --git a/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs b/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs index 7cd9bb533a807..78cb0e3b9d605 100644 --- a/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs @@ -18,7 +18,7 @@ use fixedbitset::FixedBitSet; use risingwave_common::util::sort_util::OrderType; use risingwave_pb::stream_plan::stream_node::PbNodeBody; -use super::generic::{self, GenericPlanRef, PlanWindowFunction}; +use super::generic::{self, PlanWindowFunction}; use super::stream::prelude::*; use super::utils::{impl_distill_by_unit, TableCatalogBuilder}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; diff --git a/src/frontend/src/optimizer/plan_node/stream_fs_fetch.rs b/src/frontend/src/optimizer/plan_node/stream_fs_fetch.rs index 82f2bd208b548..43c14686e2a22 100644 --- a/src/frontend/src/optimizer/plan_node/stream_fs_fetch.rs +++ b/src/frontend/src/optimizer/plan_node/stream_fs_fetch.rs @@ -101,8 +101,6 @@ impl StreamNode for StreamFsFetch { source_id: source_catalog.id, source_name: source_catalog.name.clone(), state_table: Some( - // `StreamFsSource` will do range scan according to assigned vnodes, so we need to set - // the key for distributing data to different vnodes. generic::Source::infer_internal_table_catalog(true) .with_id(state.gen_table_id_wrapped()) .to_internal_table_prost(), @@ -117,6 +115,7 @@ impl StreamNode for StreamFsFetch { .collect_vec(), with_properties: source_catalog.with_properties.clone().into_iter().collect(), rate_limit: self.base.ctx().overwrite_options().streaming_rate_limit, + secret_refs: Default::default(), }); NodeBody::StreamFsFetch(StreamFsFetchNode { node_inner: source_inner, diff --git a/src/frontend/src/optimizer/plan_node/stream_group_topn.rs b/src/frontend/src/optimizer/plan_node/stream_group_topn.rs index e48d23d14e04e..8500e24b0fd9f 100644 --- a/src/frontend/src/optimizer/plan_node/stream_group_topn.rs +++ b/src/frontend/src/optimizer/plan_node/stream_group_topn.rs @@ -16,9 +16,8 @@ use fixedbitset::FixedBitSet; use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; -use super::generic::{DistillUnit, GenericPlanRef, TopNLimit}; +use super::generic::{DistillUnit, TopNLimit}; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{plan_node_name, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanTreeNodeUnary, StreamNode}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs b/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs index d5d7bde249a17..eb69d5c259bb6 100644 --- a/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs +++ b/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs @@ -17,14 +17,13 @@ use itertools::Itertools; use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::stream::prelude::*; use super::utils::{childless_record, plan_node_name, watermark_pretty, Distill}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::error::{ErrorCode, Result}; use crate::expr::{ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::stream::StreamPlanRef; use crate::stream_fragmenter::BuildFragmentGraphState; use crate::utils::{ColIndexMapping, ColIndexMappingRewriteExt, IndexSet}; diff --git a/src/frontend/src/optimizer/plan_node/stream_hash_join.rs b/src/frontend/src/optimizer/plan_node/stream_hash_join.rs index d5e5e9838ac66..b803fccef7b07 100644 --- a/src/frontend/src/optimizer/plan_node/stream_hash_join.rs +++ b/src/frontend/src/optimizer/plan_node/stream_hash_join.rs @@ -20,9 +20,8 @@ use risingwave_pb::plan_common::JoinType; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::{DeltaExpression, HashJoinNode, PbInequalityPair}; -use super::generic::{GenericPlanRef, Join}; +use super::generic::Join; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, plan_node_name, watermark_pretty, Distill}; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, StreamDeltaJoin, StreamNode, diff --git a/src/frontend/src/optimizer/plan_node/stream_hop_window.rs b/src/frontend/src/optimizer/plan_node/stream_hop_window.rs index 9cb5800cc7220..0cdddf77ed0e5 100644 --- a/src/frontend/src/optimizer/plan_node/stream_hop_window.rs +++ b/src/frontend/src/optimizer/plan_node/stream_hop_window.rs @@ -17,9 +17,7 @@ use risingwave_common::util::column_index_mapping::ColIndexMapping; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::HopWindowNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprVisitor}; diff --git a/src/frontend/src/optimizer/plan_node/stream_materialize.rs b/src/frontend/src/optimizer/plan_node/stream_materialize.rs index 3da52ef4f55a2..e5a2496916adc 100644 --- a/src/frontend/src/optimizer/plan_node/stream_materialize.rs +++ b/src/frontend/src/optimizer/plan_node/stream_materialize.rs @@ -27,14 +27,13 @@ use risingwave_pb::stream_plan::stream_node::PbNodeBody; use super::derive::derive_columns; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, Distill}; use super::{reorganize_elements_id, ExprRewritable, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::catalog::table_catalog::{TableCatalog, TableType, TableVersion}; use crate::error::Result; use crate::optimizer::plan_node::derive::derive_pk; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::GenericPlanRef; +use crate::optimizer::plan_node::utils::plan_has_backfill_leaf_nodes; use crate::optimizer::plan_node::{PlanBase, PlanNodeMeta}; use crate::optimizer::property::{Cardinality, Distribution, Order, RequiredDist}; use crate::stream_fragmenter::BuildFragmentGraphState; @@ -86,6 +85,14 @@ impl StreamMaterialize { let input = reorganize_elements_id(input); let columns = derive_columns(input.schema(), out_names, &user_cols)?; + let create_type = if matches!(table_type, TableType::MaterializedView) + && input.ctx().session_ctx().config().background_ddl() + && plan_has_backfill_leaf_nodes(&input) + { + CreateType::Background + } else { + CreateType::Foreground + }; let table = Self::derive_table_catalog( input.clone(), name, @@ -100,6 +107,7 @@ impl StreamMaterialize { None, cardinality, retention_seconds, + create_type, )?; Ok(Self::new(input, table)) @@ -141,6 +149,7 @@ impl StreamMaterialize { version, Cardinality::unknown(), // unknown cardinality for tables retention_seconds, + CreateType::Foreground, )?; Ok(Self::new(input, table)) @@ -212,6 +221,7 @@ impl StreamMaterialize { version: Option, cardinality: Cardinality, retention_seconds: Option, + create_type: CreateType, ) -> Result { let input = rewritten_input; @@ -261,7 +271,7 @@ impl StreamMaterialize { created_at_epoch: None, initialized_at_epoch: None, cleaned_by_watermark: false, - create_type: CreateType::Foreground, // Will be updated in the handler itself. + create_type, stream_job_status: StreamJobStatus::Creating, description: None, incoming_sinks: vec![], diff --git a/src/frontend/src/optimizer/plan_node/stream_now.rs b/src/frontend/src/optimizer/plan_node/stream_now.rs index d27321c08d06b..22a0d2c5fb0fb 100644 --- a/src/frontend/src/optimizer/plan_node/stream_now.rs +++ b/src/frontend/src/optimizer/plan_node/stream_now.rs @@ -14,46 +14,39 @@ use fixedbitset::FixedBitSet; use pretty_xmlish::XmlNode; -use risingwave_common::catalog::{Field, Schema}; -use risingwave_common::types::DataType; +use risingwave_common::types::Datum; +use risingwave_common::util::value_encoding::DatumToProtoExt; +use risingwave_pb::stream_plan::now_node::PbMode as PbNowMode; use risingwave_pb::stream_plan::stream_node::NodeBody; -use risingwave_pb::stream_plan::NowNode; +use risingwave_pb::stream_plan::{PbNowModeGenerateSeries, PbNowModeUpdateCurrent, PbNowNode}; +use super::generic::Mode; use super::stream::prelude::*; use super::utils::{childless_record, Distill, TableCatalogBuilder}; -use super::{ExprRewritable, LogicalNow, PlanBase, StreamNode}; +use super::{generic, ExprRewritable, PlanBase, StreamNode}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; use crate::optimizer::plan_node::utils::column_names_pretty; -use crate::optimizer::property::{Distribution, FunctionalDependencySet}; +use crate::optimizer::property::Distribution; use crate::stream_fragmenter::BuildFragmentGraphState; -use crate::OptimizerContextRef; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StreamNow { pub base: PlanBase, + core: generic::Now, } impl StreamNow { - pub fn new(_logical: LogicalNow, ctx: OptimizerContextRef) -> Self { - let schema = Schema::new(vec![Field { - data_type: DataType::Timestamptz, - name: String::from("now"), - sub_fields: vec![], - type_name: String::default(), - }]); + pub fn new(core: generic::Now) -> Self { let mut watermark_columns = FixedBitSet::with_capacity(1); watermark_columns.set(0, true); - let base = PlanBase::new_stream( - ctx, - schema, - Some(vec![]), - FunctionalDependencySet::default(), + let base = PlanBase::new_stream_with_core( + &core, Distribution::Single, - false, - false, // TODO(rc): derive EOWC property from input + core.mode.is_generate_series(), // append only + core.mode.is_generate_series(), // emit on window close watermark_columns, ); - Self { base } + Self { base, core } } } @@ -83,8 +76,18 @@ impl StreamNode for StreamNow { let table_catalog = internal_table_catalog_builder .build(dist_keys, 0) .with_id(state.gen_table_id_wrapped()); - NodeBody::Now(NowNode { + NodeBody::Now(PbNowNode { state_table: Some(table_catalog.to_internal_table_prost()), + mode: Some(match &self.core.mode { + Mode::UpdateCurrent => PbNowMode::UpdateCurrent(PbNowModeUpdateCurrent {}), + Mode::GenerateSeries { + start_timestamp, + interval, + } => PbNowMode::GenerateSeries(PbNowModeGenerateSeries { + start_timestamp: Some(Datum::Some((*start_timestamp).into()).to_protobuf()), + interval: Some(Datum::Some((*interval).into()).to_protobuf()), + }), + }), }) } } diff --git a/src/frontend/src/optimizer/plan_node/stream_over_window.rs b/src/frontend/src/optimizer/plan_node/stream_over_window.rs index 7173e3a46e0cd..be6c63bcb50de 100644 --- a/src/frontend/src/optimizer/plan_node/stream_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/stream_over_window.rs @@ -23,7 +23,6 @@ use super::stream::prelude::*; use super::utils::{impl_distill_by_unit, TableCatalogBuilder}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::GenericPlanRef; use crate::stream_fragmenter::BuildFragmentGraphState; use crate::TableCatalog; diff --git a/src/frontend/src/optimizer/plan_node/stream_project.rs b/src/frontend/src/optimizer/plan_node/stream_project.rs index 1b97835e9896f..e8ff1df6e82db 100644 --- a/src/frontend/src/optimizer/plan_node/stream_project.rs +++ b/src/frontend/src/optimizer/plan_node/stream_project.rs @@ -17,9 +17,7 @@ use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::ProjectNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{ @@ -40,10 +38,15 @@ pub struct StreamProject { watermark_derivations: Vec<(usize, usize)>, /// Nondecreasing expression indices. `Project` can produce watermarks for these expressions. nondecreasing_exprs: Vec, + /// Whether there are likely no-op updates in the output chunks, so that eliminating them with + /// `StreamChunk::eliminate_adjacent_noop_update` could be beneficial. + noop_update_hint: bool, } impl Distill for StreamProject { fn distill<'a>(&self) -> XmlNode<'a> { + let verbose = self.base.ctx().is_explain_verbose(); + let schema = self.schema(); let mut vec = self.core.fields_pretty(schema); if let Some(display_output_watermarks) = @@ -51,12 +54,27 @@ impl Distill for StreamProject { { vec.push(("output_watermarks", display_output_watermarks)); } + if verbose && self.noop_update_hint { + vec.push(("noop_update_hint", "true".into())); + } childless_record("StreamProject", vec) } } impl StreamProject { pub fn new(core: generic::Project) -> Self { + Self::new_inner(core, false) + } + + /// Set the `noop_update_hint` flag to the given value. + pub fn with_noop_update_hint(self, noop_update_hint: bool) -> Self { + Self { + noop_update_hint, + ..self + } + } + + fn new_inner(core: generic::Project, noop_update_hint: bool) -> Self { let input = core.input.clone(); let distribution = core .i2o_col_mapping() @@ -92,11 +110,13 @@ impl StreamProject { input.emit_on_window_close(), watermark_columns, ); + StreamProject { base, core, watermark_derivations, nondecreasing_exprs, + noop_update_hint, } } @@ -107,6 +127,10 @@ impl StreamProject { pub fn exprs(&self) -> &Vec { &self.core.exprs } + + pub fn noop_update_hint(&self) -> bool { + self.noop_update_hint + } } impl PlanTreeNodeUnary for StreamProject { @@ -117,7 +141,7 @@ impl PlanTreeNodeUnary for StreamProject { fn clone_with_input(&self, input: PlanRef) -> Self { let mut core = self.core.clone(); core.input = input; - Self::new(core) + Self::new_inner(core, self.noop_update_hint) } } impl_plan_tree_node_for_unary! {StreamProject} @@ -134,6 +158,7 @@ impl StreamNode for StreamProject { watermark_input_cols, watermark_output_cols, nondecreasing_exprs: self.nondecreasing_exprs.iter().map(|i| *i as _).collect(), + noop_update_hint: self.noop_update_hint, }) } } @@ -146,7 +171,7 @@ impl ExprRewritable for StreamProject { fn rewrite_exprs(&self, r: &mut dyn ExprRewriter) -> PlanRef { let mut core = self.core.clone(); core.rewrite_exprs(r); - Self::new(core).into() + Self::new_inner(core, self.noop_update_hint).into() } } diff --git a/src/frontend/src/optimizer/plan_node/stream_share.rs b/src/frontend/src/optimizer/plan_node/stream_share.rs index 395a32bc9be8a..5bf575f622bce 100644 --- a/src/frontend/src/optimizer/plan_node/stream_share.rs +++ b/src/frontend/src/optimizer/plan_node/stream_share.rs @@ -16,7 +16,6 @@ use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::PbStreamNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; use super::utils::Distill; use super::{generic, ExprRewritable, PlanRef, PlanTreeNodeUnary, StreamExchange, StreamNode}; diff --git a/src/frontend/src/optimizer/plan_node/stream_sink.rs b/src/frontend/src/optimizer/plan_node/stream_sink.rs index e0bd1eb225e07..49378ab0a53ed 100644 --- a/src/frontend/src/optimizer/plan_node/stream_sink.rs +++ b/src/frontend/src/optimizer/plan_node/stream_sink.rs @@ -37,15 +37,15 @@ use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::SinkLogStoreType; use super::derive::{derive_columns, derive_pk}; -use super::generic::{self, GenericPlanRef}; use super::stream::prelude::*; use super::utils::{ childless_record, infer_kv_log_store_table_catalog_inner, Distill, IndicesDisplay, }; -use super::{ExprRewritable, PlanBase, PlanRef, StreamNode, StreamProject}; +use super::{generic, ExprRewritable, PlanBase, PlanRef, StreamNode, StreamProject}; use crate::error::{ErrorCode, Result}; use crate::expr::{ExprImpl, FunctionCall, InputRef}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; +use crate::optimizer::plan_node::utils::plan_has_backfill_leaf_nodes; use crate::optimizer::plan_node::PlanTreeNodeUnary; use crate::optimizer::property::{Distribution, Order, RequiredDist}; use crate::stream_fragmenter::BuildFragmentGraphState; @@ -148,18 +148,19 @@ impl IcebergPartitionInfo { )) } } + #[inline] fn find_column_idx_by_name(columns: &[ColumnCatalog], col_name: &str) -> Result { columns - .iter() - .position(|col| col.column_desc.name == col_name) - .ok_or_else(|| { - ErrorCode::SinkError(Box::new(Error::new( - ErrorKind::InvalidInput, - format!("Sink primary key column not found: {}. Please use ',' as the delimiter for different primary key columns.", col_name) - ))) + .iter() + .position(|col| col.column_desc.name == col_name) + .ok_or_else(|| { + ErrorCode::SinkError(Box::new(Error::new( + ErrorKind::InvalidInput, + format!("Sink primary key column not found: {}. Please use ',' as the delimiter for different primary key columns.", col_name), + ))) .into() - }) + }) } /// [`StreamSink`] represents a table/connector sink at the very end of the graph. @@ -179,6 +180,7 @@ impl StreamSink { .into_stream() .expect("input should be stream plan") .clone_with_new_plan_id(); + Self { base, input, @@ -315,6 +317,7 @@ impl StreamSink { let (pk, _) = derive_pk(input.clone(), user_order_by, &columns); let mut downstream_pk = Self::parse_downstream_pk(&columns, properties.get(DOWNSTREAM_PK_KEY))?; + let mut extra_partition_col_idx = None; let required_dist = match input.distribution() { Distribution::Single => RequiredDist::single(), @@ -371,7 +374,9 @@ impl StreamSink { }; let input = required_dist.enforce_if_not_satisfies(input, &Order::any())?; let distribution_key = input.distribution().dist_column_indices().to_vec(); - let create_type = if input.ctx().session_ctx().config().background_ddl() { + let create_type = if input.ctx().session_ctx().config().background_ddl() + && plan_has_backfill_leaf_nodes(&input) + { CreateType::Background } else { CreateType::Foreground @@ -444,7 +449,7 @@ impl StreamSink { let (user_defined_append_only, user_force_append_only, syntax_legacy) = match format_desc { Some(f) => ( f.format == SinkFormat::AppendOnly, - Self::is_user_force_append_only(&WithOptions::from_inner(f.options.clone()))?, + Self::is_user_force_append_only(&WithOptions::new(f.options.clone()))?, false, ), None => ( @@ -465,16 +470,20 @@ impl StreamSink { (false, true, false) => { Err(ErrorCode::SinkError(Box::new(Error::new( ErrorKind::InvalidInput, - format!("The sink cannot be append-only. Please add \"force_append_only='true'\" in {} options to force the sink to be append-only. Notice that this will cause the sink executor to drop any UPDATE or DELETE message.", if syntax_legacy {"WITH"} else {"FORMAT ENCODE"}), + format!( + "The sink cannot be append-only. Please add \"force_append_only='true'\" in {} options to force the sink to be append-only. \ + Notice that this will cause the sink executor to drop DELETE messages and convert UPDATE messages to INSERT.", + if syntax_legacy { "WITH" } else { "FORMAT ENCODE" } + ), ))) - .into()) + .into()) } (_, false, true) => { Err(ErrorCode::SinkError(Box::new(Error::new( ErrorKind::InvalidInput, - format!("Cannot force the sink to be append-only without \"{}\".", if syntax_legacy {"type='append-only'"} else {"FORMAT PLAIN"}), + format!("Cannot force the sink to be append-only without \"{}\".", if syntax_legacy { "type='append-only'" } else { "FORMAT PLAIN" }), ))) - .into()) + .into()) } } } @@ -612,6 +621,7 @@ mod test { }, ] } + #[test] fn test_iceberg_convert_to_expression() { let partition_type = StructType::new(vec![ diff --git a/src/frontend/src/optimizer/plan_node/stream_source.rs b/src/frontend/src/optimizer/plan_node/stream_source.rs index 7b918a6312a63..83ad14886872a 100644 --- a/src/frontend/src/optimizer/plan_node/stream_source.rs +++ b/src/frontend/src/optimizer/plan_node/stream_source.rs @@ -17,8 +17,9 @@ use std::rc::Rc; use fixedbitset::FixedBitSet; use itertools::Itertools; use pretty_xmlish::{Pretty, XmlNode}; +use risingwave_common::catalog::ColumnCatalog; use risingwave_common::util::iter_util::ZipEqFast; -use risingwave_connector::parser::additional_columns::add_partition_offset_cols; +use risingwave_connector::parser::additional_columns::source_add_partition_offset_cols; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::{PbStreamSource, SourceNode}; @@ -46,12 +47,13 @@ impl StreamSource { if let Some(source_catalog) = &core.catalog && source_catalog.info.is_shared() { - let (columns_exist, additional_columns) = - add_partition_offset_cols(&core.column_catalog, &source_catalog.connector_name()); - for (existed, mut c) in columns_exist.into_iter().zip_eq_fast(additional_columns) { - c.is_hidden = true; + let (columns_exist, additional_columns) = source_add_partition_offset_cols( + &core.column_catalog, + &source_catalog.connector_name(), + ); + for (existed, c) in columns_exist.into_iter().zip_eq_fast(additional_columns) { if !existed { - core.column_catalog.push(c); + core.column_catalog.push(ColumnCatalog::hidden(c)); } } } @@ -93,8 +95,6 @@ impl StreamNode for StreamSource { source_id: source_catalog.id, source_name: source_catalog.name.clone(), state_table: Some( - // `StreamSource` can write all data to the same vnode - // but it is ok because we only do point get on each key rather than range scan. generic::Source::infer_internal_table_catalog(false) .with_id(state.gen_table_id_wrapped()) .to_internal_table_prost(), @@ -109,6 +109,7 @@ impl StreamNode for StreamSource { .collect_vec(), with_properties: source_catalog.with_properties.clone().into_iter().collect(), rate_limit: self.base.ctx().overwrite_options().streaming_rate_limit, + secret_refs: Default::default(), }); PbNodeBody::Source(SourceNode { source_inner }) } diff --git a/src/frontend/src/optimizer/plan_node/stream_source_scan.rs b/src/frontend/src/optimizer/plan_node/stream_source_scan.rs index 597517ce1e3f8..0449648798ea7 100644 --- a/src/frontend/src/optimizer/plan_node/stream_source_scan.rs +++ b/src/frontend/src/optimizer/plan_node/stream_source_scan.rs @@ -17,11 +17,11 @@ use std::rc::Rc; use fixedbitset::FixedBitSet; use itertools::Itertools; use pretty_xmlish::{Pretty, XmlNode}; -use risingwave_common::catalog::Field; +use risingwave_common::catalog::{ColumnCatalog, Field}; use risingwave_common::types::DataType; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common::util::sort_util::OrderType; -use risingwave_connector::parser::additional_columns::add_partition_offset_cols; +use risingwave_connector::parser::additional_columns::source_add_partition_offset_cols; use risingwave_pb::stream_plan::stream_node::{NodeBody, PbNodeBody}; use risingwave_pb::stream_plan::PbStreamNode; @@ -58,12 +58,13 @@ impl StreamSourceScan { if let Some(source_catalog) = &core.catalog && source_catalog.info.is_shared() { - let (columns_exist, additional_columns) = - add_partition_offset_cols(&core.column_catalog, &source_catalog.connector_name()); - for (existed, mut c) in columns_exist.into_iter().zip_eq_fast(additional_columns) { - c.is_hidden = true; + let (columns_exist, additional_columns) = source_add_partition_offset_cols( + &core.column_catalog, + &source_catalog.connector_name(), + ); + for (existed, c) in columns_exist.into_iter().zip_eq_fast(additional_columns) { if !existed { - core.column_catalog.push(c); + core.column_catalog.push(ColumnCatalog::hidden(c)); } } } @@ -94,13 +95,9 @@ impl StreamSourceScan { .expect("source scan should have source cataglog") } + /// The state is different from but similar to `StreamSource`. + /// Refer to [`generic::Source::infer_internal_table_catalog`] for more details. pub fn infer_internal_table_catalog() -> TableCatalog { - // note that source's internal table is to store partition_id -> offset mapping and its - // schema is irrelevant to input schema - // On the premise of ensuring that the materialized_source data can be cleaned up, keep the - // state in source. - // Source state doesn't maintain retention_seconds, internal_table_subset function only - // returns retention_seconds so default is used here let mut builder = TableCatalogBuilder::default(); let key = Field { @@ -119,7 +116,7 @@ impl StreamSourceScan { let ordered_col_idx = builder.add_column(&key); builder.add_column(&value); builder.add_order_column(ordered_col_idx, OrderType::ascending()); - // read prefix hint is 0. We need to scan all data in the state table. + // Hacky: read prefix hint is 0, because we need to scan all data in the state table. builder.build(vec![], 0) } @@ -160,6 +157,7 @@ impl StreamSourceScan { .collect_vec(), with_properties: source_catalog.with_properties.clone().into_iter().collect(), rate_limit: self.base.ctx().overwrite_options().streaming_rate_limit, + secret_refs: Default::default(), }; let fields = self.schema().to_prost(); diff --git a/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs b/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs index d5fce4eecdc1c..8ce0997b7fe1f 100644 --- a/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs +++ b/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs @@ -22,7 +22,6 @@ use super::utils::impl_distill_by_unit; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::PhysicalPlanRef; use crate::optimizer::property::RequiredDist; use crate::stream_fragmenter::BuildFragmentGraphState; diff --git a/src/frontend/src/optimizer/plan_node/stream_subscription.rs b/src/frontend/src/optimizer/plan_node/stream_subscription.rs deleted file mode 100644 index 8b165d5bbacbc..0000000000000 --- a/src/frontend/src/optimizer/plan_node/stream_subscription.rs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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::assert_matches::assert_matches; -use std::collections::HashSet; - -use fixedbitset::FixedBitSet; -use itertools::Itertools; -use pretty_xmlish::{Pretty, XmlNode}; -use risingwave_common::catalog::{ColumnCatalog, TableId, UserId}; -use risingwave_pb::stream_plan::stream_node::PbNodeBody; - -use super::derive::{derive_columns, derive_pk}; -use super::expr_visitable::ExprVisitable; -use super::stream::prelude::{GenericPlanRef, PhysicalPlanRef}; -use super::utils::{ - childless_record, infer_kv_log_store_table_catalog_inner, Distill, IndicesDisplay, -}; -use super::{ExprRewritable, PlanBase, PlanTreeNodeUnary, Stream, StreamNode}; -use crate::catalog::subscription_catalog::{SubscriptionCatalog, SubscriptionId}; -use crate::error::Result; -use crate::optimizer::property::{Distribution, Order, RequiredDist}; -use crate::stream_fragmenter::BuildFragmentGraphState; -use crate::{PlanRef, TableCatalog, WithOptions}; - -/// [`StreamSubscription`] represents a subscription at the very end of the graph. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamSubscription { - pub base: PlanBase, - input: PlanRef, - subscription_catalog: SubscriptionCatalog, -} - -impl StreamSubscription { - #[must_use] - pub fn new(input: PlanRef, subscription_catalog: SubscriptionCatalog) -> Self { - let base = input - .plan_base() - .into_stream() - .expect("input should be stream plan") - .clone_with_new_plan_id(); - Self { - base, - input, - subscription_catalog, - } - } - - pub fn subscription_catalog(&self) -> SubscriptionCatalog { - self.subscription_catalog.clone() - } - - #[allow(clippy::too_many_arguments)] - pub fn create( - database_id: u32, - schema_id: u32, - dependent_relations: HashSet, - input: PlanRef, - name: String, - subscription_from_name: String, - user_distributed_by: RequiredDist, - user_order_by: Order, - user_cols: FixedBitSet, - out_names: Vec, - definition: String, - properties: WithOptions, - user_id: UserId, - ) -> Result { - let columns = derive_columns(input.schema(), out_names, &user_cols)?; - let (input, subscription) = Self::derive_subscription_catalog( - database_id, - schema_id, - dependent_relations, - input, - user_distributed_by, - name, - subscription_from_name, - user_order_by, - columns, - definition, - properties, - user_id, - )?; - Ok(Self::new(input, subscription)) - } - - #[allow(clippy::too_many_arguments)] - fn derive_subscription_catalog( - database_id: u32, - schema_id: u32, - dependent_relations: HashSet, - input: PlanRef, - user_distributed_by: RequiredDist, - name: String, - subscription_from_name: String, - user_order_by: Order, - columns: Vec, - definition: String, - properties: WithOptions, - user_id: UserId, - ) -> Result<(PlanRef, SubscriptionCatalog)> { - let (pk, _) = derive_pk(input.clone(), user_order_by, &columns); - let required_dist = match input.distribution() { - Distribution::Single => RequiredDist::single(), - _ => { - assert_matches!(user_distributed_by, RequiredDist::Any); - RequiredDist::shard_by_key(input.schema().len(), input.expect_stream_key()) - } - }; - let input = required_dist.enforce_if_not_satisfies(input, &Order::any())?; - let distribution_key = input.distribution().dist_column_indices().to_vec(); - let subscription_desc = SubscriptionCatalog { - database_id, - schema_id, - dependent_relations: dependent_relations.into_iter().collect(), - id: SubscriptionId::placeholder(), - name, - subscription_from_name, - definition, - columns, - plan_pk: pk, - distribution_key, - properties: properties.into_inner(), - owner: user_id, - initialized_at_epoch: None, - created_at_epoch: None, - created_at_cluster_version: None, - initialized_at_cluster_version: None, - subscription_internal_table_name: None, - }; - Ok((input, subscription_desc)) - } - - /// The table schema is: | epoch | seq id | row op | subscription columns | - /// Pk is: | epoch | seq id | - fn infer_kv_log_store_table_catalog(&self) -> TableCatalog { - infer_kv_log_store_table_catalog_inner(&self.input, &self.subscription_catalog.columns) - } -} - -impl PlanTreeNodeUnary for StreamSubscription { - fn input(&self) -> PlanRef { - self.input.clone() - } - - fn clone_with_input(&self, input: PlanRef) -> Self { - Self::new(input, self.subscription_catalog.clone()) - // TODO(nanderstabel): Add assertions (assert_eq!) - } -} - -impl_plan_tree_node_for_unary! { StreamSubscription } - -impl Distill for StreamSubscription { - fn distill<'a>(&self) -> XmlNode<'a> { - let column_names = self - .subscription_catalog - .columns - .iter() - .map(|col| col.name_with_hidden().to_string()) - .map(Pretty::from) - .collect(); - let column_names = Pretty::Array(column_names); - let mut vec = Vec::with_capacity(2); - vec.push(("columns", column_names)); - let pk = IndicesDisplay { - indices: &self - .subscription_catalog - .plan_pk - .iter() - .map(|k| k.column_index) - .collect_vec(), - schema: self.base.schema(), - }; - vec.push(("pk", pk.distill())); - childless_record("Streamsubscription", vec) - } -} - -impl StreamNode for StreamSubscription { - fn to_stream_prost_body(&self, state: &mut BuildFragmentGraphState) -> PbNodeBody { - use risingwave_pb::stream_plan::*; - - // We need to create a table for subscription with a kv log store. - let table = self - .infer_kv_log_store_table_catalog() - .with_id(state.gen_table_id_wrapped()); - - PbNodeBody::Subscription(SubscriptionNode { - subscription_catalog: Some(self.subscription_catalog.to_proto()), - log_store_table: Some(table.to_internal_table_prost()), - }) - } -} - -impl ExprRewritable for StreamSubscription {} - -impl ExprVisitable for StreamSubscription {} diff --git a/src/frontend/src/optimizer/plan_node/stream_table_scan.rs b/src/frontend/src/optimizer/plan_node/stream_table_scan.rs index 458707910c089..1e93514f6c0f7 100644 --- a/src/frontend/src/optimizer/plan_node/stream_table_scan.rs +++ b/src/frontend/src/optimizer/plan_node/stream_table_scan.rs @@ -34,7 +34,7 @@ use crate::optimizer::plan_node::utils::{IndicesDisplay, TableCatalogBuilder}; use crate::optimizer::property::{Distribution, DistributionDisplay}; use crate::scheduler::SchedulerResult; use crate::stream_fragmenter::BuildFragmentGraphState; -use crate::{Explain, TableCatalog}; +use crate::TableCatalog; /// `StreamTableScan` is a virtual plan node to represent a stream table scan. It will be converted /// to stream scan + merge node (for upstream materialize) + batch table scan when converting to `MView` @@ -147,6 +147,7 @@ impl StreamTableScan { /// | 1002 | Int64(1) | t | 10 | /// | 1003 | Int64(1) | t | 10 | /// | 1003 | Int64(1) | t | 10 | + /// /// Eventually we should track progress per vnode, to support scaling with both mview and /// the corresponding `no_shuffle_backfill`. /// However this is not high priority, since we are working on supporting arrangement backfill, @@ -240,12 +241,7 @@ impl StreamTableScan { let stream_key = self .stream_key() - .unwrap_or_else(|| { - panic!( - "should always have a stream key in the stream plan but not, sub plan: {}", - PlanRef::from(self.clone()).explain_to_string() - ) - }) + .unwrap_or(&[]) .iter() .map(|x| *x as u32) .collect_vec(); diff --git a/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs b/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs index ce8753b9ddbc8..f94dbba36cb79 100644 --- a/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs +++ b/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs @@ -20,9 +20,7 @@ use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::TemporalJoinNode; use risingwave_sqlparser::ast::AsOf; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary}; use crate::expr::{Expr, ExprRewriter, ExprVisitor}; @@ -74,7 +72,7 @@ impl StreamTemporalJoin { let base = PlanBase::new_stream_with_core( &core, dist, - true, + append_only, false, // TODO(rc): derive EOWC property from input watermark_columns, ); diff --git a/src/frontend/src/optimizer/plan_node/stream_union.rs b/src/frontend/src/optimizer/plan_node/stream_union.rs index 7a317a2677495..1c269ec0c5ad2 100644 --- a/src/frontend/src/optimizer/plan_node/stream_union.rs +++ b/src/frontend/src/optimizer/plan_node/stream_union.rs @@ -19,9 +19,7 @@ use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::UnionNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanRef}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs b/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs index ed7e367f93715..ffb08776b3fe5 100644 --- a/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs +++ b/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs @@ -20,7 +20,6 @@ use risingwave_pb::catalog::WatermarkDesc; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill, TableCatalogBuilder}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{ExprDisplay, ExprImpl}; diff --git a/src/frontend/src/optimizer/plan_node/utils.rs b/src/frontend/src/optimizer/plan_node/utils.rs index f16aed4521180..9ac61ed759c6d 100644 --- a/src/frontend/src/optimizer/plan_node/utils.rs +++ b/src/frontend/src/optimizer/plan_node/utils.rs @@ -31,6 +31,7 @@ use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use crate::catalog::table_catalog::TableType; use crate::catalog::{ColumnId, TableCatalog, TableId}; use crate::optimizer::property::{Cardinality, Order, RequiredDist}; +use crate::optimizer::StreamScanType; use crate::utils::{Condition, IndexSet}; #[derive(Default)] @@ -41,7 +42,6 @@ pub struct TableCatalogBuilder { value_indices: Option>, vnode_col_idx: Option, column_names: HashMap, - read_prefix_len_hint: usize, watermark_columns: Option, dist_key_in_pk: Option>, } @@ -136,7 +136,7 @@ impl TableCatalogBuilder { /// anticipated read prefix pattern (number of fields) for the table, which can be utilized for /// implementing the table's bloom filter or other storage optimization techniques. pub fn build(self, distribution_key: Vec, read_prefix_len_hint: usize) -> TableCatalog { - assert!(self.read_prefix_len_hint <= self.pk.len()); + assert!(read_prefix_len_hint <= self.pk.len()); let watermark_columns = match self.watermark_columns { Some(w) => w, None => FixedBitSet::with_capacity(self.columns.len()), @@ -297,6 +297,7 @@ pub(crate) fn sum_affected_row(dml: PlanRef) -> Result { order_by: vec![], filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }; let agg = Agg::new(vec![sum_agg], IndexSet::empty(), dml); let batch_agg = BatchSimpleAgg::new(agg); @@ -371,3 +372,19 @@ pub fn infer_kv_log_store_table_catalog_inner( table_catalog_builder.build(dist_key, read_prefix_len_hint) } + +/// Check that all leaf nodes must be stream table scan, +/// since that plan node maps to `backfill` executor, which supports recovery. +pub(crate) fn plan_has_backfill_leaf_nodes(plan: &PlanRef) -> bool { + if plan.inputs().is_empty() { + if let Some(scan) = plan.as_stream_table_scan() { + scan.stream_scan_type() == StreamScanType::Backfill + || scan.stream_scan_type() == StreamScanType::ArrangementBackfill + } else { + false + } + } else { + assert!(!plan.inputs().is_empty()); + plan.inputs().iter().all(plan_has_backfill_leaf_nodes) + } +} diff --git a/src/frontend/src/optimizer/plan_rewriter/mod.rs b/src/frontend/src/optimizer/plan_rewriter/mod.rs index 81c0809bae86d..360c61d3121b0 100644 --- a/src/frontend/src/optimizer/plan_rewriter/mod.rs +++ b/src/frontend/src/optimizer/plan_rewriter/mod.rs @@ -56,11 +56,20 @@ macro_rules! def_rewriter { pub trait PlanRewriter { paste! { fn rewrite(&mut self, plan: PlanRef) -> PlanRef{ - match plan.node_type() { - $( - PlanNodeType::[<$convention $name>] => self.[](plan.downcast_ref::<[<$convention $name>]>().unwrap()), - )* - } + use risingwave_common::util::recursive::{tracker, Recurse}; + use crate::session::current::notice_to_user; + + tracker!().recurse(|t| { + if t.depth_reaches(PLAN_DEPTH_THRESHOLD) { + notice_to_user(PLAN_TOO_DEEP_NOTICE); + } + + match plan.node_type() { + $( + PlanNodeType::[<$convention $name>] => self.[](plan.downcast_ref::<[<$convention $name>]>().unwrap()), + )* + } + }) } $( diff --git a/src/frontend/src/optimizer/plan_visitor/cardinality_visitor.rs b/src/frontend/src/optimizer/plan_visitor/cardinality_visitor.rs index c81bf539a5713..07459b59b1d5f 100644 --- a/src/frontend/src/optimizer/plan_visitor/cardinality_visitor.rs +++ b/src/frontend/src/optimizer/plan_visitor/cardinality_visitor.rs @@ -174,8 +174,12 @@ impl PlanVisitor for CardinalityVisitor { } } - fn visit_logical_now(&mut self, _plan: &plan_node::LogicalNow) -> Cardinality { - 1.into() + fn visit_logical_now(&mut self, plan: &plan_node::LogicalNow) -> Cardinality { + if plan.max_one_row() { + 1.into() + } else { + Cardinality::unknown() + } } fn visit_logical_expand(&mut self, plan: &plan_node::LogicalExpand) -> Cardinality { diff --git a/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs b/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs index d0a4358b4582a..c41e1c5b36463 100644 --- a/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs +++ b/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs @@ -17,7 +17,7 @@ use risingwave_common::types::DataType; use super::{DefaultBehavior, Merge}; use crate::optimizer::plan_node::generic::GenericPlanRef; -use crate::optimizer::plan_node::{PlanNode, *}; +use crate::optimizer::plan_node::*; use crate::optimizer::plan_visitor::PlanVisitor; #[derive(Debug, Clone, Default)] diff --git a/src/frontend/src/optimizer/plan_visitor/mod.rs b/src/frontend/src/optimizer/plan_visitor/mod.rs index 6156454fd3e80..63a0484cfdfd5 100644 --- a/src/frontend/src/optimizer/plan_visitor/mod.rs +++ b/src/frontend/src/optimizer/plan_visitor/mod.rs @@ -93,11 +93,20 @@ macro_rules! def_visitor { paste! { fn visit(&mut self, plan: PlanRef) -> Self::Result { - match plan.node_type() { - $( - PlanNodeType::[<$convention $name>] => self.[](plan.downcast_ref::<[<$convention $name>]>().unwrap()), - )* - } + use risingwave_common::util::recursive::{tracker, Recurse}; + use crate::session::current::notice_to_user; + + tracker!().recurse(|t| { + if t.depth_reaches(PLAN_DEPTH_THRESHOLD) { + notice_to_user(PLAN_TOO_DEEP_NOTICE); + } + + match plan.node_type() { + $( + PlanNodeType::[<$convention $name>] => self.[](plan.downcast_ref::<[<$convention $name>]>().unwrap()), + )* + } + }) } $( diff --git a/src/frontend/src/optimizer/property/distribution.rs b/src/frontend/src/optimizer/property/distribution.rs index 4999e1d8630bf..cb24644356aa7 100644 --- a/src/frontend/src/optimizer/property/distribution.rs +++ b/src/frontend/src/optimizer/property/distribution.rs @@ -51,9 +51,9 @@ use generic::PhysicalPlanRef; use itertools::Itertools; use risingwave_batch::worker_manager::worker_node_manager::WorkerNodeSelector; use risingwave_common::catalog::{FieldDisplay, Schema, TableId}; -use risingwave_common::hash::ParallelUnitId; +use risingwave_common::hash::WorkerSlotId; use risingwave_pb::batch_plan::exchange_info::{ - ConsistentHashInfo, Distribution as DistributionPb, DistributionMode, HashInfo, + ConsistentHashInfo, Distribution as PbDistribution, DistributionMode, HashInfo, }; use risingwave_pb::batch_plan::ExchangeInfo; @@ -62,7 +62,6 @@ use crate::catalog::catalog_service::CatalogReader; use crate::catalog::FragmentId; use crate::error::Result; use crate::optimizer::property::Order; -use crate::optimizer::PlanRef; /// the distribution property provided by a operator. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -132,7 +131,7 @@ impl Distribution { !key.is_empty(), "hash key should not be empty, use `Single` instead" ); - Some(DistributionPb::HashInfo(HashInfo { + Some(PbDistribution::HashInfo(HashInfo { output_count, key: key.iter().map(|num| *num as u32).collect(), })) @@ -149,14 +148,17 @@ impl Distribution { let vnode_mapping = worker_node_manager .fragment_mapping(Self::get_fragment_id(catalog_reader, table_id)?)?; - let pu2id_map: HashMap = vnode_mapping + let worker_slot_to_id_map: HashMap = vnode_mapping .iter_unique() .enumerate() - .map(|(i, pu)| (pu, i as u32)) + .map(|(i, worker_slot_id)| (worker_slot_id, i as u32)) .collect(); - Some(DistributionPb::ConsistentHashInfo(ConsistentHashInfo { - vmap: vnode_mapping.iter().map(|x| pu2id_map[&x]).collect_vec(), + Some(PbDistribution::ConsistentHashInfo(ConsistentHashInfo { + vmap: vnode_mapping + .iter() + .map(|id| worker_slot_to_id_map[&id]) + .collect_vec(), key: key.iter().map(|num| *num as u32).collect(), })) } @@ -204,7 +206,7 @@ impl Distribution { fn get_fragment_id(catalog_reader: &CatalogReader, table_id: &TableId) -> Result { catalog_reader .read_guard() - .get_table_by_id(table_id) + .get_any_table_by_id(table_id) .map(|table| table.fragment_id) .map_err(Into::into) } diff --git a/src/frontend/src/optimizer/property/order.rs b/src/frontend/src/optimizer/property/order.rs index 1a657e190fac4..68f2120df567b 100644 --- a/src/frontend/src/optimizer/property/order.rs +++ b/src/frontend/src/optimizer/property/order.rs @@ -22,7 +22,6 @@ use risingwave_pb::common::PbColumnOrder; use super::super::plan_node::*; use crate::error::Result; -use crate::optimizer::PlanRef; // TODO(rc): use this type to replace all `Vec` #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] diff --git a/src/frontend/src/optimizer/rule/agg_group_by_simplify_rule.rs b/src/frontend/src/optimizer/rule/agg_group_by_simplify_rule.rs index b700e36987826..d55c00d10bb2d 100644 --- a/src/frontend/src/optimizer/rule/agg_group_by_simplify_rule.rs +++ b/src/frontend/src/optimizer/rule/agg_group_by_simplify_rule.rs @@ -54,6 +54,7 @@ impl Rule for AggGroupBySimplifyRule { order_by: vec![], filter: Condition::true_cond(), direct_args: vec![], + user_defined: None, }); } } diff --git a/src/frontend/src/optimizer/rule/apply_agg_transpose_rule.rs b/src/frontend/src/optimizer/rule/apply_agg_transpose_rule.rs index c53357234e350..48c3366d1d825 100644 --- a/src/frontend/src/optimizer/rule/apply_agg_transpose_rule.rs +++ b/src/frontend/src/optimizer/rule/apply_agg_transpose_rule.rs @@ -106,10 +106,11 @@ impl Rule for ApplyAggTransposeRule { correlated_id, correlated_indices.clone(), false, + false, ) .translate_apply(left, eq_predicates) } else { - LogicalApply::new( + LogicalApply::create( left, input, JoinType::Inner, @@ -118,7 +119,6 @@ impl Rule for ApplyAggTransposeRule { correlated_indices.clone(), false, ) - .into() }; let group_agg = { @@ -147,7 +147,8 @@ impl Rule for ApplyAggTransposeRule { } AggKind::ArrayAgg | AggKind::JsonbAgg - | AggKind::JsonbObjectAgg => { + | AggKind::JsonbObjectAgg + | AggKind::UserDefined => { let input_ref = InputRef::new(pos_of_constant_column, DataType::Int32); let cond = FunctionCall::new(ExprType::IsNotNull, vec![input_ref.into()]).unwrap(); agg_call.filter.conjunctions.push(cond.into()); diff --git a/src/frontend/src/optimizer/rule/apply_dedup_transpose_rule.rs b/src/frontend/src/optimizer/rule/apply_dedup_transpose_rule.rs index 798744168c424..1b793cddd2816 100644 --- a/src/frontend/src/optimizer/rule/apply_dedup_transpose_rule.rs +++ b/src/frontend/src/optimizer/rule/apply_dedup_transpose_rule.rs @@ -57,7 +57,7 @@ impl Rule for ApplyDedupTransposeRule { return None; } - let new_apply = LogicalApply::new( + let new_apply = LogicalApply::create( left, dedup_input, JoinType::Inner, @@ -65,8 +65,7 @@ impl Rule for ApplyDedupTransposeRule { correlated_id, correlated_indices, false, - ) - .into(); + ); let new_dedup = { let mut new_dedup_cols: Vec = (0..apply_left_len).collect(); diff --git a/src/frontend/src/optimizer/rule/apply_expand_transpose_rule.rs b/src/frontend/src/optimizer/rule/apply_expand_transpose_rule.rs index 694fc803f87ff..9f03d9e420981 100644 --- a/src/frontend/src/optimizer/rule/apply_expand_transpose_rule.rs +++ b/src/frontend/src/optimizer/rule/apply_expand_transpose_rule.rs @@ -61,7 +61,7 @@ impl Rule for ApplyExpandTransposeRule { return None; } - let new_apply: PlanRef = LogicalApply::new( + let new_apply: PlanRef = LogicalApply::create( left, expand_input, JoinType::Inner, @@ -69,8 +69,7 @@ impl Rule for ApplyExpandTransposeRule { correlated_id, correlated_indices, false, - ) - .into(); + ); let new_apply_schema_len = new_apply.schema().len(); diff --git a/src/frontend/src/optimizer/rule/apply_hop_window_transpose_rule.rs b/src/frontend/src/optimizer/rule/apply_hop_window_transpose_rule.rs index b3d5d0841bdc5..634a8c6feb45c 100644 --- a/src/frontend/src/optimizer/rule/apply_hop_window_transpose_rule.rs +++ b/src/frontend/src/optimizer/rule/apply_hop_window_transpose_rule.rs @@ -62,7 +62,7 @@ impl Rule for ApplyHopWindowTransposeRule { return None; } - let new_apply = LogicalApply::new( + let new_apply = LogicalApply::create( left, hop_window_input, JoinType::Inner, @@ -70,8 +70,7 @@ impl Rule for ApplyHopWindowTransposeRule { correlated_id, correlated_indices, false, - ) - .into(); + ); let new_hop_window = LogicalHopWindow::create( new_apply, diff --git a/src/frontend/src/optimizer/rule/apply_limit_transpose_rule.rs b/src/frontend/src/optimizer/rule/apply_limit_transpose_rule.rs index 8dc01c195cee5..56395ed61a6b6 100644 --- a/src/frontend/src/optimizer/rule/apply_limit_transpose_rule.rs +++ b/src/frontend/src/optimizer/rule/apply_limit_transpose_rule.rs @@ -63,7 +63,7 @@ impl Rule for ApplyLimitTransposeRule { return None; } - let new_apply = LogicalApply::new( + let new_apply = LogicalApply::create( left, limit_input, JoinType::Inner, @@ -71,8 +71,7 @@ impl Rule for ApplyLimitTransposeRule { correlated_id, correlated_indices, false, - ) - .into(); + ); let new_topn = { // use the first column as an order to provide determinism for streaming queries. diff --git a/src/frontend/src/optimizer/rule/apply_over_window_transpose_rule.rs b/src/frontend/src/optimizer/rule/apply_over_window_transpose_rule.rs index 34ae1902b4d27..bb848561ce0b6 100644 --- a/src/frontend/src/optimizer/rule/apply_over_window_transpose_rule.rs +++ b/src/frontend/src/optimizer/rule/apply_over_window_transpose_rule.rs @@ -58,7 +58,7 @@ impl Rule for ApplyOverWindowTransposeRule { let apply_left_len = left.schema().len(); let apply_left_schema = left.schema().clone(); - let new_apply = LogicalApply::new( + let new_apply = LogicalApply::create( left, window_input, JoinType::Inner, @@ -66,8 +66,7 @@ impl Rule for ApplyOverWindowTransposeRule { correlated_id, correlated_indices, false, - ) - .into(); + ); let new_over_window = { // Shift index of window functions' `InputRef` with `apply_left_len`. diff --git a/src/frontend/src/optimizer/rule/apply_topn_transpose_rule.rs b/src/frontend/src/optimizer/rule/apply_topn_transpose_rule.rs index 61b887af7ea54..dbdb180b1358c 100644 --- a/src/frontend/src/optimizer/rule/apply_topn_transpose_rule.rs +++ b/src/frontend/src/optimizer/rule/apply_topn_transpose_rule.rs @@ -49,7 +49,8 @@ impl Rule for ApplyTopNTransposeRule { apply.clone().decompose(); assert_eq!(join_type, JoinType::Inner); let topn: &LogicalTopN = right.as_logical_top_n()?; - let (topn_input, limit, offset, with_ties, mut order, group_key) = topn.clone().decompose(); + let (topn_input, limit, offset, with_ties, mut order, mut group_key) = + topn.clone().decompose(); let apply_left_len = left.schema().len(); @@ -57,7 +58,7 @@ impl Rule for ApplyTopNTransposeRule { return None; } - let new_apply = LogicalApply::new( + let new_apply = LogicalApply::create( left, topn_input, JoinType::Inner, @@ -65,8 +66,7 @@ impl Rule for ApplyTopNTransposeRule { correlated_id, correlated_indices, false, - ) - .into(); + ); let new_topn = { // shift index of topn's `InputRef` with `apply_left_len`. @@ -74,6 +74,7 @@ impl Rule for ApplyTopNTransposeRule { .column_orders .iter_mut() .for_each(|ord| ord.column_index += apply_left_len); + group_key.iter_mut().for_each(|idx| *idx += apply_left_len); let new_group_key = (0..apply_left_len).chain(group_key).collect_vec(); LogicalTopN::new(new_apply, limit, offset, with_ties, order, new_group_key) }; diff --git a/src/frontend/src/optimizer/rule/intersect_to_semi_join_rule.rs b/src/frontend/src/optimizer/rule/intersect_to_semi_join_rule.rs index 29ccbc066ec36..1d7d385aec627 100644 --- a/src/frontend/src/optimizer/rule/intersect_to_semi_join_rule.rs +++ b/src/frontend/src/optimizer/rule/intersect_to_semi_join_rule.rs @@ -50,14 +50,14 @@ impl Rule for IntersectToSemiJoinRule { impl IntersectToSemiJoinRule { pub(crate) fn gen_null_safe_equal(left: PlanRef, right: PlanRef) -> ExprImpl { - (left + let arms = (left .schema() .fields() .iter() .zip_eq_debug(right.schema().fields()) .enumerate()) - .fold(None, |expr, (i, (left_field, right_field))| { - let equal = ExprImpl::FunctionCall(Box::new(FunctionCall::new_unchecked( + .map(|(i, (left_field, right_field))| { + ExprImpl::FunctionCall(Box::new(FunctionCall::new_unchecked( ExprType::IsNotDistinctFrom, vec![ ExprImpl::InputRef(Box::new(InputRef::new(i, left_field.data_type()))), @@ -67,16 +67,9 @@ impl IntersectToSemiJoinRule { ))), ], Boolean, - ))); - - match expr { - None => Some(equal), - Some(expr) => Some(ExprImpl::FunctionCall(Box::new( - FunctionCall::new_unchecked(ExprType::And, vec![expr, equal], Boolean), - ))), - } - }) - .unwrap() + ))) + }); + ExprImpl::and(arms) } } diff --git a/src/frontend/src/optimizer/rule/logical_filter_expression_simplify_rule.rs b/src/frontend/src/optimizer/rule/logical_filter_expression_simplify_rule.rs index ebc724c200481..67f7e40ed3455 100644 --- a/src/frontend/src/optimizer/rule/logical_filter_expression_simplify_rule.rs +++ b/src/frontend/src/optimizer/rule/logical_filter_expression_simplify_rule.rs @@ -33,6 +33,7 @@ impl Rule for LogicalFilterExpressionSimplifyRule { /// The pattern we aim to optimize, e.g., /// 1. (NOT (e)) OR (e) => True /// 2. (NOT (e)) AND (e) => False + /// /// NOTE: `e` should only contain at most a single column /// otherwise we will not conduct the optimization fn apply(&self, plan: PlanRef) -> Option { @@ -153,6 +154,7 @@ fn check_optimizable_pattern(e1: ExprImpl, e2: ExprImpl) -> (bool, Option True /// 2. False and (...) | (...) and False => False +/// /// NOTE: the `True` and `False` here not only represent a single `ExprImpl::Literal` /// but represent every `ExprImpl` that can be *evaluated* to `ScalarImpl::Bool` /// during optimization phase as well diff --git a/src/frontend/src/optimizer/rule/min_max_on_index_rule.rs b/src/frontend/src/optimizer/rule/min_max_on_index_rule.rs index f7f9ffb02dddd..7c663ea714b6d 100644 --- a/src/frontend/src/optimizer/rule/min_max_on_index_rule.rs +++ b/src/frontend/src/optimizer/rule/min_max_on_index_rule.rs @@ -124,6 +124,7 @@ impl MinMaxOnIndexRule { conjunctions: vec![], }, direct_args: vec![], + user_defined: None, }], IndexSet::empty(), topn.into(), @@ -194,6 +195,7 @@ impl MinMaxOnIndexRule { conjunctions: vec![], }, direct_args: vec![], + user_defined: None, }], IndexSet::empty(), topn.into(), diff --git a/src/frontend/src/optimizer/rule/mod.rs b/src/frontend/src/optimizer/rule/mod.rs index 9364a6f2b7f5b..fd06402c7497f 100644 --- a/src/frontend/src/optimizer/rule/mod.rs +++ b/src/frontend/src/optimizer/rule/mod.rs @@ -92,6 +92,7 @@ pub use top_n_on_index_rule::*; mod stream; pub use stream::bushy_tree_join_ordering_rule::*; pub use stream::filter_with_now_to_join_rule::*; +pub use stream::generate_series_with_now_rule::*; pub use stream::split_now_and_rule::*; pub use stream::split_now_or_rule::*; pub use stream::stream_project_merge_rule::*; @@ -203,6 +204,7 @@ macro_rules! for_all_rules { , { SplitNowAndRule } , { SplitNowOrRule } , { FilterWithNowToJoinRule } + , { GenerateSeriesWithNowRule } , { TopNOnIndexRule } , { TrivialProjectToValuesRule } , { UnionInputValuesMergeRule } diff --git a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs index 7823c7f41f7b4..c9cb4edc860cd 100644 --- a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs +++ b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs @@ -24,7 +24,6 @@ use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprType, FunctionCall, InputRef use crate::optimizer::plan_expr_visitor::Strong; use crate::optimizer::plan_node::generic::{Agg, GenericPlanNode, GenericPlanRef}; use crate::optimizer::plan_visitor::{PlanCorrelatedIdFinder, PlanVisitor}; -use crate::optimizer::PlanRef; use crate::utils::{Condition, IndexSet}; /// Pull up correlated predicates from the right agg side of Apply to the `on` clause of Join. diff --git a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs index b5df4f1738072..d6b59b9553911 100644 --- a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs +++ b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs @@ -20,7 +20,6 @@ use super::{BoxedRule, Rule}; use crate::expr::{CorrelatedId, CorrelatedInputRef, Expr, ExprImpl, ExprRewriter, InputRef}; use crate::optimizer::plan_node::generic::GenericPlanRef; use crate::optimizer::plan_visitor::{PlanCorrelatedIdFinder, PlanVisitor}; -use crate::optimizer::PlanRef; use crate::utils::Condition; /// This rule is for pattern: Apply->Project->Filter. diff --git a/src/frontend/src/optimizer/rule/stream/filter_with_now_to_join_rule.rs b/src/frontend/src/optimizer/rule/stream/filter_with_now_to_join_rule.rs index 498696589c81b..cbdb65b4528a5 100644 --- a/src/frontend/src/optimizer/rule/stream/filter_with_now_to_join_rule.rs +++ b/src/frontend/src/optimizer/rule/stream/filter_with_now_to_join_rule.rs @@ -18,7 +18,7 @@ use risingwave_pb::plan_common::JoinType; use crate::expr::{ try_derive_watermark, ExprRewriter, FunctionCall, InputRef, WatermarkDerivation, }; -use crate::optimizer::plan_node::generic::GenericPlanRef; +use crate::optimizer::plan_node::generic::{self, GenericPlanRef}; use crate::optimizer::plan_node::{LogicalFilter, LogicalJoin, LogicalNow}; use crate::optimizer::rule::{BoxedRule, Rule}; use crate::optimizer::PlanRef; @@ -63,7 +63,7 @@ impl Rule for FilterWithNowToJoinRule { for now_filter in now_filters { new_plan = LogicalJoin::new( new_plan, - LogicalNow::new(plan.ctx()).into(), + LogicalNow::new(generic::Now::update_current(plan.ctx())).into(), JoinType::LeftSemi, Condition { conjunctions: vec![now_filter.into()], diff --git a/src/frontend/src/optimizer/rule/stream/generate_series_with_now_rule.rs b/src/frontend/src/optimizer/rule/stream/generate_series_with_now_rule.rs new file mode 100644 index 0000000000000..665967f6660b0 --- /dev/null +++ b/src/frontend/src/optimizer/rule/stream/generate_series_with_now_rule.rs @@ -0,0 +1,86 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_common::types::DataType; +use risingwave_pb::expr::table_function::PbType as PbTableFuncType; + +use crate::expr::{Expr, ExprRewriter}; +use crate::optimizer::plan_node::{generic, LogicalNow}; +use crate::optimizer::rule::{BoxedRule, Rule}; +use crate::PlanRef; + +pub struct GenerateSeriesWithNowRule {} +impl Rule for GenerateSeriesWithNowRule { + fn apply(&self, plan: PlanRef) -> Option { + let ctx = plan.ctx(); + let table_func = plan.as_logical_table_function()?.table_function(); + + if !table_func.args.iter().any(|arg| arg.has_now()) { + return None; + } + + if !(table_func.function_type == PbTableFuncType::GenerateSeries + && table_func.args.len() == 3 + && table_func.args[0].return_type() == DataType::Timestamptz + && table_func.args[1].is_now() + && table_func.args[2].return_type() == DataType::Interval) + { + // only convert `generate_series(const timestamptz, now(), const interval)` + ctx.warn_to_user( + "`now()` is currently only supported in `generate_series(timestamptz, timestamptz, interval)` function as `stop`. \ + You may not using it correctly. Please kindly check the document." + ); + return None; + } + + let start_timestamp = ctx + .session_timezone() + .rewrite_expr(table_func.args[0].clone()) + .try_fold_const() + .transpose() + .ok() + .flatten() + .flatten(); + let interval = ctx + .session_timezone() + .rewrite_expr(table_func.args[2].clone()) + .try_fold_const() + .transpose() + .ok() + .flatten() + .flatten(); + + if start_timestamp.is_none() || interval.is_none() { + ctx.warn_to_user( + "When using `generate_series` with `now()`, the `start` and `step` must be non-NULL constants", + ); + return None; + } + + Some( + LogicalNow::new(generic::Now::generate_series( + ctx, + start_timestamp.unwrap().into_timestamptz(), + interval.unwrap().into_interval(), + )) + .into(), + ) + } +} + +impl GenerateSeriesWithNowRule { + pub fn create() -> BoxedRule { + Box::new(Self {}) + } +} diff --git a/src/frontend/src/optimizer/rule/stream/mod.rs b/src/frontend/src/optimizer/rule/stream/mod.rs index cc86298e766e8..539d9048cff60 100644 --- a/src/frontend/src/optimizer/rule/stream/mod.rs +++ b/src/frontend/src/optimizer/rule/stream/mod.rs @@ -14,6 +14,7 @@ pub(crate) mod bushy_tree_join_ordering_rule; pub(crate) mod filter_with_now_to_join_rule; +pub(crate) mod generate_series_with_now_rule; pub(crate) mod split_now_and_rule; pub(crate) mod split_now_or_rule; pub(crate) mod stream_project_merge_rule; diff --git a/src/frontend/src/optimizer/rule/stream/split_now_or_rule.rs b/src/frontend/src/optimizer/rule/stream/split_now_or_rule.rs index 36c88211848a1..ea63119980f90 100644 --- a/src/frontend/src/optimizer/rule/stream/split_now_or_rule.rs +++ b/src/frontend/src/optimizer/rule/stream/split_now_or_rule.rs @@ -57,7 +57,7 @@ impl Rule for SplitNowOrRule { return None; } - let (mut now, others): (Vec, Vec) = + let (now, others): (Vec, Vec) = disjunctions.into_iter().partition(|x| x.count_nows() != 0); // Only support now in one arm of disjunctions @@ -70,22 +70,10 @@ impl Rule for SplitNowOrRule { // + A & !B & !C ... &!Z // + B | C ... | Z - let mut arm1 = now.pop().unwrap(); - for pred in &others { - let not_pred: ExprImpl = - FunctionCall::new_unchecked(ExprType::Not, vec![pred.clone()], DataType::Boolean) - .into(); - arm1 = - FunctionCall::new_unchecked(ExprType::And, vec![arm1, not_pred], DataType::Boolean) - .into(); - } - - let arm2 = others - .into_iter() - .reduce(|a, b| { - FunctionCall::new_unchecked(ExprType::Or, vec![a, b], DataType::Boolean).into() - }) - .unwrap(); + let arm1 = ExprImpl::and(now.into_iter().chain(others.iter().map(|pred| { + FunctionCall::new_unchecked(ExprType::Not, vec![pred.clone()], DataType::Boolean).into() + }))); + let arm2 = ExprImpl::or(others); let share = LogicalShare::create(input); let filter1 = LogicalFilter::create_with_expr(share.clone(), arm1); diff --git a/src/frontend/src/optimizer/rule/stream/stream_project_merge_rule.rs b/src/frontend/src/optimizer/rule/stream/stream_project_merge_rule.rs index 0eba52e193d22..91ab942e3a7fc 100644 --- a/src/frontend/src/optimizer/rule/stream/stream_project_merge_rule.rs +++ b/src/frontend/src/optimizer/rule/stream/stream_project_merge_rule.rs @@ -47,7 +47,15 @@ impl Rule for StreamProjectMergeRule { .map(|expr| subst.rewrite_expr(expr)) .collect(); let logical_project = generic::Project::new(exprs, inner_project.input()); - Some(StreamProject::new(logical_project).into()) + + // If either of the projects has the hint, we should keep it. + let noop_update_hint = outer_project.noop_update_hint() || inner_project.noop_update_hint(); + + Some( + StreamProject::new(logical_project) + .with_noop_update_hint(noop_update_hint) + .into(), + ) } } diff --git a/src/frontend/src/optimizer/rule/translate_apply_rule.rs b/src/frontend/src/optimizer/rule/translate_apply_rule.rs index 53a656fa296a9..876ca7d6285b2 100644 --- a/src/frontend/src/optimizer/rule/translate_apply_rule.rs +++ b/src/frontend/src/optimizer/rule/translate_apply_rule.rs @@ -54,6 +54,9 @@ pub struct TranslateApplyRule { impl Rule for TranslateApplyRule { fn apply(&self, plan: PlanRef) -> Option { let apply: &LogicalApply = plan.as_logical_apply()?; + if apply.translated() { + return None; + } let mut left: PlanRef = apply.left(); let right: PlanRef = apply.right(); let apply_left_len = left.schema().len(); @@ -167,6 +170,15 @@ impl TranslateApplyRule { data_types, index, ) + } else if let Some(apply) = plan.as_logical_apply() { + Self::rewrite_apply( + apply, + correlated_indices, + offset, + index_mapping, + data_types, + index, + ) } else if let Some(scan) = plan.as_logical_scan() { Self::rewrite_scan( scan, @@ -199,18 +211,6 @@ impl TranslateApplyRule { data_types: &mut HashMap, index: &mut usize, ) -> Option { - // Only accept join which doesn't generate null columns. - if !matches!( - join.join_type(), - JoinType::Inner - | JoinType::LeftSemi - | JoinType::RightSemi - | JoinType::LeftAnti - | JoinType::RightAnti - ) { - return None; - } - // TODO: Do we need to take the `on` into account? let left_len = join.left().schema().len(); let (left_idxs, right_idxs): (Vec<_>, Vec<_>) = required_col_idx @@ -222,28 +222,99 @@ impl TranslateApplyRule { indices.iter_mut().for_each(|index| *index -= left_len); offset += left_len; } - if let Some(join) = plan.as_logical_join() { - Self::rewrite_join(join, indices, offset, index_mapping, data_types, index) - } else if let Some(scan) = plan.as_logical_scan() { - Self::rewrite_scan(scan, indices, offset, index_mapping, data_types, index) - } else { - None - } + Self::rewrite(&plan, indices, offset, index_mapping, data_types, index) }; match (left_idxs.is_empty(), right_idxs.is_empty()) { - (true, false) => rewrite(join.right(), right_idxs, true), - (false, true) => rewrite(join.left(), left_idxs, false), + (true, false) => { + // Only accept join which doesn't generate null columns. + match join.join_type() { + JoinType::Inner + | JoinType::LeftSemi + | JoinType::RightSemi + | JoinType::LeftAnti + | JoinType::RightAnti + | JoinType::RightOuter => rewrite(join.right(), right_idxs, true), + JoinType::LeftOuter | JoinType::FullOuter => None, + JoinType::Unspecified => unreachable!(), + } + } + (false, true) => { + // Only accept join which doesn't generate null columns. + match join.join_type() { + JoinType::Inner + | JoinType::LeftSemi + | JoinType::RightSemi + | JoinType::LeftAnti + | JoinType::RightAnti + | JoinType::LeftOuter => rewrite(join.left(), left_idxs, false), + JoinType::RightOuter | JoinType::FullOuter => None, + JoinType::Unspecified => unreachable!(), + } + } (false, false) => { - let left = rewrite(join.left(), left_idxs, false)?; - let right = rewrite(join.right(), right_idxs, true)?; - let new_join = - LogicalJoin::new(left, right, join.join_type(), Condition::true_cond()); - Some(new_join.into()) + // Only accept join which doesn't generate null columns. + match join.join_type() { + JoinType::Inner + | JoinType::LeftSemi + | JoinType::RightSemi + | JoinType::LeftAnti + | JoinType::RightAnti => { + let left = rewrite(join.left(), left_idxs, false)?; + let right = rewrite(join.right(), right_idxs, true)?; + let new_join = + LogicalJoin::new(left, right, join.join_type(), Condition::true_cond()); + Some(new_join.into()) + } + JoinType::LeftOuter | JoinType::RightOuter | JoinType::FullOuter => None, + JoinType::Unspecified => unreachable!(), + } } _ => None, } } + /// ```text + /// LogicalApply + /// / \ + /// LogicalApply RHS1 + /// / \ + /// LHS RHS2 + /// ``` + /// + /// A common structure of multi scalar subqueries is a chain of `LogicalApply`. To avoid exponential growth of the domain operator, we need to rewrite the apply and try to simplify the domain as much as possible. + /// We use a top-down apply order to rewrite the apply, so that we don't need to handle operator like project and aggregation generated by the domain calculation. + /// As a cost, we need to add a flag `translated` to the apply operator to remind `translate_apply_rule` that the apply has been translated. + fn rewrite_apply( + apply: &LogicalApply, + required_col_idx: Vec, + offset: usize, + index_mapping: &mut ColIndexMapping, + data_types: &mut HashMap, + index: &mut usize, + ) -> Option { + // TODO: Do we need to take the `on` into account? + let left_len = apply.left().schema().len(); + let (left_idxs, right_idxs): (Vec<_>, Vec<_>) = required_col_idx + .into_iter() + .partition(|idx| *idx < left_len); + if !left_idxs.is_empty() && right_idxs.is_empty() { + // Deal with multi scalar subqueries + match apply.join_type() { + JoinType::Inner | JoinType::LeftSemi | JoinType::LeftAnti | JoinType::LeftOuter => { + let plan = apply.left(); + Self::rewrite(&plan, left_idxs, offset, index_mapping, data_types, index) + } + JoinType::RightOuter + | JoinType::RightAnti + | JoinType::RightSemi + | JoinType::FullOuter => None, + JoinType::Unspecified => unreachable!(), + } + } else { + None + } + } + fn rewrite_scan( scan: &LogicalScan, required_col_idx: Vec, diff --git a/src/connector/src/parser/debezium/operators.rs b/src/frontend/src/planner/changelog.rs similarity index 62% rename from src/connector/src/parser/debezium/operators.rs rename to src/frontend/src/planner/changelog.rs index 8bb6230e9486f..cddfa629938f8 100644 --- a/src/connector/src/parser/debezium/operators.rs +++ b/src/frontend/src/planner/changelog.rs @@ -11,8 +11,15 @@ // 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 crate::binder::Relation; +use crate::error::Result; +use crate::optimizer::plan_node::LogicalChangeLog; +use crate::{PlanRef, Planner}; -pub const DEBEZIUM_READ_OP: &str = "r"; -pub const DEBEZIUM_CREATE_OP: &str = "c"; -pub const DEBEZIUM_UPDATE_OP: &str = "u"; -pub const DEBEZIUM_DELETE_OP: &str = "d"; +impl Planner { + pub(super) fn plan_changelog(&mut self, relation: Relation) -> Result { + let root = self.plan_relation(relation)?; + let plan = LogicalChangeLog::create(root); + Ok(plan) + } +} diff --git a/src/frontend/src/planner/delete.rs b/src/frontend/src/planner/delete.rs index 9e777774eee41..c4c34346ec1d2 100644 --- a/src/frontend/src/planner/delete.rs +++ b/src/frontend/src/planner/delete.rs @@ -68,7 +68,7 @@ impl Planner { plan.schema().names() }; - let root = PlanRoot::new(plan, dist, Order::any(), out_fields, out_names); + let root = PlanRoot::new_with_logical_plan(plan, dist, Order::any(), out_fields, out_names); Ok(root) } } diff --git a/src/frontend/src/planner/insert.rs b/src/frontend/src/planner/insert.rs index 98a89202ff292..078cbd6c6ce0a 100644 --- a/src/frontend/src/planner/insert.rs +++ b/src/frontend/src/planner/insert.rs @@ -53,7 +53,7 @@ impl Planner { } else { plan.schema().names() }; - let root = PlanRoot::new(plan, dist, Order::any(), out_fields, out_names); + let root = PlanRoot::new_with_logical_plan(plan, dist, Order::any(), out_fields, out_names); Ok(root) } } diff --git a/src/frontend/src/planner/mod.rs b/src/frontend/src/planner/mod.rs index d2f695faa5ab2..7ccbb905cb47d 100644 --- a/src/frontend/src/planner/mod.rs +++ b/src/frontend/src/planner/mod.rs @@ -18,9 +18,11 @@ use crate::binder::{BoundStatement, ShareId}; use crate::error::Result; use crate::optimizer::{OptimizerContextRef, PlanRoot}; +mod changelog; mod delete; mod insert; mod query; +mod recursive_union; mod relation; mod select; mod set_expr; diff --git a/src/frontend/src/planner/query.rs b/src/frontend/src/planner/query.rs index cf6f46bc671ac..2edde6105d190 100644 --- a/src/frontend/src/planner/query.rs +++ b/src/frontend/src/planner/query.rs @@ -68,7 +68,8 @@ impl Planner { // Do not output projected_row_id hidden column. out_fields.set(0, false); } - let root = PlanRoot::new(plan, RequiredDist::Any, order, out_fields, out_names); + let root = + PlanRoot::new_with_logical_plan(plan, RequiredDist::Any, order, out_fields, out_names); Ok(root) } } diff --git a/src/frontend/src/planner/recursive_union.rs b/src/frontend/src/planner/recursive_union.rs new file mode 100644 index 0000000000000..655e50e951a4b --- /dev/null +++ b/src/frontend/src/planner/recursive_union.rs @@ -0,0 +1,33 @@ +// Copyright 2024 RisingWave Labs +// +// 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 crate::binder::{BoundSetExpr, ShareId}; +use crate::error::Result; +use crate::optimizer::plan_node::LogicalRecursiveUnion; +use crate::{PlanRef, Planner}; + +impl Planner { + pub(super) fn plan_recursive_union( + &mut self, + base: BoundSetExpr, + recursive: BoundSetExpr, + id: ShareId, + ) -> Result { + let base = self.plan_set_expr(base, vec![], &[])?; + let recursive = self.plan_set_expr(recursive, vec![], &[])?; + let plan = LogicalRecursiveUnion::create(base, recursive, id); + self.ctx.insert_rcte_cache_plan(id, plan.clone()); + Ok(plan) + } +} diff --git a/src/frontend/src/planner/relation.rs b/src/frontend/src/planner/relation.rs index d593a7308f39f..7941d2837a068 100644 --- a/src/frontend/src/planner/relation.rs +++ b/src/frontend/src/planner/relation.rs @@ -22,15 +22,15 @@ use risingwave_common::types::{DataType, Interval, ScalarImpl}; use risingwave_sqlparser::ast::AsOf; use crate::binder::{ - BoundBaseTable, BoundJoin, BoundShare, BoundSource, BoundSystemTable, BoundWatermark, - BoundWindowTableFunction, Relation, WindowTableFunctionKind, + BoundBackCteRef, BoundBaseTable, BoundJoin, BoundShare, BoundShareInput, BoundSource, + BoundSystemTable, BoundWatermark, BoundWindowTableFunction, Relation, WindowTableFunctionKind, }; use crate::error::{ErrorCode, Result}; use crate::expr::{Expr, ExprImpl, ExprType, FunctionCall, InputRef}; use crate::optimizer::plan_node::generic::SourceNodeKind; use crate::optimizer::plan_node::{ - LogicalApply, LogicalHopWindow, LogicalJoin, LogicalProject, LogicalScan, LogicalShare, - LogicalSource, LogicalSysScan, LogicalTableFunction, LogicalValues, PlanRef, + LogicalApply, LogicalCteRef, LogicalHopWindow, LogicalJoin, LogicalProject, LogicalScan, + LogicalShare, LogicalSource, LogicalSysScan, LogicalTableFunction, LogicalValues, PlanRef, }; use crate::optimizer::property::Cardinality; use crate::planner::Planner; @@ -57,9 +57,7 @@ impl Planner { Relation::Watermark(tf) => self.plan_watermark(*tf), // note that rcte (i.e., RecursiveUnion) is included *implicitly* in share. Relation::Share(share) => self.plan_share(*share), - Relation::BackCteRef(..) => { - bail_not_implemented!(issue = 15135, "recursive CTE is not supported") - } + Relation::BackCteRef(cte_ref) => self.plan_cte_ref(*cte_ref), } } @@ -197,41 +195,62 @@ impl Planner { Ok(LogicalTableFunction::new(*tf, with_ordinality, self.ctx()).into()) } expr => { - let mut schema = Schema { + let schema = Schema { // TODO: should be named fields: vec![Field::unnamed(expr.return_type())], }; - if with_ordinality { - schema - .fields - .push(Field::with_name(DataType::Int64, "ordinality")); - Ok(LogicalValues::create( - vec![vec![expr, ExprImpl::literal_bigint(1)]], - schema, - self.ctx(), - )) + let expr_return_type = expr.return_type(); + let root = LogicalValues::create(vec![vec![expr]], schema, self.ctx()); + let input_ref = ExprImpl::from(InputRef::new(0, expr_return_type.clone())); + let mut exprs = if let DataType::Struct(st) = expr_return_type { + st.iter() + .enumerate() + .map(|(i, (_, ty))| { + let idx = ExprImpl::literal_int(i.try_into().unwrap()); + let args = vec![input_ref.clone(), idx]; + FunctionCall::new_unchecked(ExprType::Field, args, ty.clone()).into() + }) + .collect() } else { - Ok(LogicalValues::create(vec![vec![expr]], schema, self.ctx())) + vec![input_ref] + }; + if with_ordinality { + exprs.push(ExprImpl::literal_bigint(1)); } + Ok(LogicalProject::create(root, exprs)) } } } pub(super) fn plan_share(&mut self, share: BoundShare) -> Result { - let Either::Left(nonrecursive_query) = share.input else { - bail_not_implemented!(issue = 15135, "recursive CTE is not supported"); - }; - let id = share.share_id; - match self.share_cache.get(&id) { - None => { - let result = self - .plan_query(nonrecursive_query)? - .into_unordered_subplan(); + match share.input { + BoundShareInput::Query(Either::Left(nonrecursive_query)) => { + let id = share.share_id; + match self.share_cache.get(&id) { + None => { + let result = self + .plan_query(nonrecursive_query)? + .into_unordered_subplan(); + let logical_share = LogicalShare::create(result); + self.share_cache.insert(id, logical_share.clone()); + Ok(logical_share) + } + Some(result) => Ok(result.clone()), + } + } + // for the recursive union in rcte + BoundShareInput::Query(Either::Right(recursive_union)) => self.plan_recursive_union( + *recursive_union.base, + *recursive_union.recursive, + share.share_id, + ), + BoundShareInput::ChangeLog(relation) => { + let id = share.share_id; + let result = self.plan_changelog(relation)?; let logical_share = LogicalShare::create(result); self.share_cache.insert(id, logical_share.clone()); Ok(logical_share) } - Some(result) => Ok(result.clone()), } } @@ -239,6 +258,12 @@ impl Planner { todo!("plan watermark"); } + pub(super) fn plan_cte_ref(&mut self, cte_ref: BoundBackCteRef) -> Result { + // TODO: this is actually duplicated from `plan_recursive_union`, refactor? + let base = self.plan_set_expr(cte_ref.base, vec![], &[])?; + Ok(LogicalCteRef::create(cte_ref.share_id, base)) + } + fn collect_col_data_types_for_tumble_window(relation: &Relation) -> Result> { let col_data_types = match relation { Relation::Source(s) => s @@ -254,10 +279,12 @@ impl Planner { .map(|col| col.data_type().clone()) .collect(), Relation::Subquery(q) => q.query.schema().data_types(), - Relation::Share(share) => match &share.input { - Either::Left(nonrecursive) => nonrecursive.schema().data_types(), - Either::Right(recursive) => recursive.schema.data_types(), - }, + Relation::Share(share) => share + .input + .fields()? + .into_iter() + .map(|(_, f)| f.data_type) + .collect(), r => { return Err(ErrorCode::BindError(format!( "Invalid input relation to tumble: {r:?}" diff --git a/src/frontend/src/planner/update.rs b/src/frontend/src/planner/update.rs index 83e36f17084db..ef735db1b5a95 100644 --- a/src/frontend/src/planner/update.rs +++ b/src/frontend/src/planner/update.rs @@ -65,7 +65,7 @@ impl Planner { plan.schema().names() }; - let root = PlanRoot::new(plan, dist, Order::any(), out_fields, out_names); + let root = PlanRoot::new_with_logical_plan(plan, dist, Order::any(), out_fields, out_names); Ok(root) } } diff --git a/src/frontend/src/scheduler/distributed/query.rs b/src/frontend/src/scheduler/distributed/query.rs index c6e866630067b..5ab5f1aa85af7 100644 --- a/src/frontend/src/scheduler/distributed/query.rs +++ b/src/frontend/src/scheduler/distributed/query.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::collections::HashMap; -use std::default::Default; use std::fmt::{Debug, Formatter}; use std::mem; use std::sync::Arc; @@ -25,7 +24,7 @@ use petgraph::Graph; use pgwire::pg_server::SessionId; use risingwave_batch::worker_manager::worker_node_manager::WorkerNodeSelector; use risingwave_common::array::DataChunk; -use risingwave_pb::batch_plan::{TaskId as TaskIdPb, TaskOutputId as TaskOutputIdPb}; +use risingwave_pb::batch_plan::{TaskId as PbTaskId, TaskOutputId as PbTaskOutputId}; use risingwave_pb::common::HostAddress; use risingwave_rpc_client::ComputeClientPoolRef; use thiserror_ext::AsReport; @@ -390,13 +389,13 @@ impl QueryRunner { /// of shutdown sender so that shutdown receiver won't be triggered. fn send_root_stage_info(&mut self, chunk_rx: Receiver>) { let root_task_output_id = { - let root_task_id_prost = TaskIdPb { + let root_task_id_prost = PbTaskId { query_id: self.query.query_id.clone().id, stage_id: self.query.root_stage_id(), task_id: ROOT_TASK_ID, }; - TaskOutputIdPb { + PbTaskOutputId { task_id: Some(root_task_id_prost), output_id: ROOT_TASK_OUTPUT_ID, } @@ -477,7 +476,7 @@ pub(crate) mod tests { ColumnCatalog, ColumnDesc, ConflictBehavior, CreateType, StreamJobStatus, DEFAULT_SUPER_USER_ID, }; - use risingwave_common::hash::ParallelUnitMapping; + use risingwave_common::hash::{WorkerSlotId, WorkerSlotMapping}; use risingwave_common::types::DataType; use risingwave_pb::common::worker_node::Property; use risingwave_pb::common::{HostAddress, ParallelUnit, WorkerNode, WorkerType}; @@ -722,10 +721,12 @@ pub(crate) mod tests { let workers = vec![worker1, worker2, worker3]; let worker_node_manager = Arc::new(WorkerNodeManager::mock(workers)); let worker_node_selector = WorkerNodeSelector::new(worker_node_manager.clone(), false); - worker_node_manager - .insert_streaming_fragment_mapping(0, ParallelUnitMapping::new_single(0)); + worker_node_manager.insert_streaming_fragment_mapping( + 0, + WorkerSlotMapping::new_single(WorkerSlotId::new(0, 0)), + ); worker_node_manager.set_serving_fragment_mapping( - vec![(0, ParallelUnitMapping::new_single(0))] + vec![(0, WorkerSlotMapping::new_single(WorkerSlotId::new(0, 0)))] .into_iter() .collect(), ); diff --git a/src/frontend/src/scheduler/distributed/query_manager.rs b/src/frontend/src/scheduler/distributed/query_manager.rs index 69d48eb70d976..86a54cf9c0f98 100644 --- a/src/frontend/src/scheduler/distributed/query_manager.rs +++ b/src/frontend/src/scheduler/distributed/query_manager.rs @@ -50,6 +50,7 @@ impl DistributedQueryStream { } impl Stream for DistributedQueryStream { + // TODO(error-handling): use a concrete error type. type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { diff --git a/src/frontend/src/scheduler/distributed/stage.rs b/src/frontend/src/scheduler/distributed/stage.rs index 193ee6417abdc..8301c5a5b9d54 100644 --- a/src/frontend/src/scheduler/distributed/stage.rs +++ b/src/frontend/src/scheduler/distributed/stage.rs @@ -32,7 +32,7 @@ use risingwave_batch::executor::ExecutorBuilder; use risingwave_batch::task::{ShutdownMsg, ShutdownSender, ShutdownToken, TaskId as TaskIdBatch}; use risingwave_batch::worker_manager::worker_node_manager::WorkerNodeSelector; use risingwave_common::array::DataChunk; -use risingwave_common::hash::ParallelUnitMapping; +use risingwave_common::hash::WorkerSlotMapping; use risingwave_common::util::addr::HostAddr; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_connector::source::SplitMetaData; @@ -40,7 +40,7 @@ use risingwave_expr::expr_context::expr_context_scope; use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::{ DistributedLookupJoinNode, ExchangeNode, ExchangeSource, MergeSortExchangeNode, PlanFragment, - PlanNode as PlanNodePb, PlanNode, TaskId as TaskIdPb, TaskOutputId, + PlanNode as PbPlanNode, PlanNode, TaskId as PbTaskId, TaskOutputId, }; use risingwave_pb::common::{BatchQueryEpoch, HostAddress, WorkerNode}; use risingwave_pb::plan_common::ExprContext; @@ -94,7 +94,7 @@ pub enum StageEvent { reason: SchedulerError, }, /// All tasks in stage finished. - Completed(StageId), + Completed(#[allow(dead_code)] StageId), } #[derive(Clone)] @@ -172,7 +172,7 @@ impl StageExecution { ctx: ExecutionContextRef, ) -> Self { let tasks = (0..stage.parallelism.unwrap()) - .map(|task_id| (task_id, TaskStatusHolder::new(task_id))) + .map(|task_id| (task_id as u64, TaskStatusHolder::new(task_id as u64))) .collect(); Self { @@ -289,12 +289,12 @@ impl StageExecution { /// /// When this method is called, all tasks should have been scheduled, and their `worker_node` /// should have been set. - pub fn all_exchange_sources_for(&self, output_id: u32) -> Vec { + pub fn all_exchange_sources_for(&self, output_id: u64) -> Vec { self.tasks .iter() .map(|(task_id, status_holder)| { let task_output_id = TaskOutputId { - task_id: Some(TaskIdPb { + task_id: Some(PbTaskId { query_id: self.stage.query_id.id.clone(), stage_id: self.stage.id, task_id: *task_id, @@ -353,24 +353,25 @@ impl StageRunner { // We let each task read one partition by setting the `vnode_ranges` of the scan node in // the task. // We schedule the task to the worker node that owns the data partition. - let parallel_unit_ids = vnode_bitmaps.keys().cloned().collect_vec(); + let worker_slot_ids = vnode_bitmaps.keys().cloned().collect_vec(); let workers = self .worker_node_manager .manager - .get_workers_by_parallel_unit_ids(¶llel_unit_ids)?; - for (i, (parallel_unit_id, worker)) in parallel_unit_ids + .get_workers_by_worker_slot_ids(&worker_slot_ids)?; + + for (i, (worker_slot_id, worker)) in worker_slot_ids .into_iter() .zip_eq_fast(workers.into_iter()) .enumerate() { - let task_id = TaskIdPb { + let task_id = PbTaskId { query_id: self.stage.query_id.id.clone(), stage_id: self.stage.id, - task_id: i as u32, + task_id: i as u64, }; - let vnode_ranges = vnode_bitmaps[¶llel_unit_id].clone(); + let vnode_ranges = vnode_bitmaps[&worker_slot_id].clone(); let plan_fragment = - self.create_plan_fragment(i as u32, Some(PartitionInfo::Table(vnode_ranges))); + self.create_plan_fragment(i as u64, Some(PartitionInfo::Table(vnode_ranges))); futures.push(self.schedule_task( task_id, plan_fragment, @@ -388,13 +389,13 @@ impl StageRunner { .chunks(chunk_size) .enumerate() { - let task_id = TaskIdPb { + let task_id = PbTaskId { query_id: self.stage.query_id.id.clone(), stage_id: self.stage.id, - task_id: id as u32, + task_id: id as u64, }; let plan_fragment = self - .create_plan_fragment(id as u32, Some(PartitionInfo::Source(split.to_vec()))); + .create_plan_fragment(id as u64, Some(PartitionInfo::Source(split.to_vec()))); let worker = self.choose_worker(&plan_fragment, id as u32, self.stage.dml_table_id)?; futures.push(self.schedule_task( @@ -406,12 +407,12 @@ impl StageRunner { } } else { for id in 0..self.stage.parallelism.unwrap() { - let task_id = TaskIdPb { + let task_id = PbTaskId { query_id: self.stage.query_id.id.clone(), stage_id: self.stage.id, - task_id: id, + task_id: id as u64, }; - let plan_fragment = self.create_plan_fragment(id, None); + let plan_fragment = self.create_plan_fragment(id as u64, None); let worker = self.choose_worker(&plan_fragment, id, self.stage.dml_table_id)?; futures.push(self.schedule_task( task_id, @@ -438,9 +439,9 @@ impl StageRunner { while let Some(status_res_inner) = all_streams.next().await { match status_res_inner { Ok(status) => { - use risingwave_pb::task_service::task_info_response::TaskStatus as TaskStatusPb; - match TaskStatusPb::try_from(status.task_status).unwrap() { - TaskStatusPb::Running => { + use risingwave_pb::task_service::task_info_response::TaskStatus as PbTaskStatus; + match PbTaskStatus::try_from(status.task_status).unwrap() { + PbTaskStatus::Running => { running_task_cnt += 1; // The task running count should always less or equal than the // registered tasks number. @@ -456,7 +457,7 @@ impl StageRunner { } } - TaskStatusPb::Finished => { + PbTaskStatus::Finished => { finished_task_cnt += 1; assert!(finished_task_cnt <= self.tasks.keys().len()); assert!(running_task_cnt >= finished_task_cnt); @@ -468,7 +469,7 @@ impl StageRunner { break; } } - TaskStatusPb::Aborted => { + PbTaskStatus::Aborted => { // Currently, the only reason that we receive an abort status is that // the task's memory usage is too high so // it's aborted. @@ -487,7 +488,7 @@ impl StageRunner { sent_signal_to_next = true; break; } - TaskStatusPb::Failed => { + PbTaskStatus::Failed => { // Task failed, we should fail whole query error!( "Task {:?} failed, reason: {:?}", @@ -505,7 +506,7 @@ impl StageRunner { sent_signal_to_next = true; break; } - TaskStatusPb::Ping => { + PbTaskStatus::Ping => { debug!("Receive ping from task {:?}", status.task_id.unwrap()); } status => { @@ -673,7 +674,7 @@ impl StageRunner { fn get_fragment_id(&self, table_id: &TableId) -> SchedulerResult { self.catalog_reader .read_guard() - .get_table_by_id(table_id) + .get_any_table_by_id(table_id) .map(|table| table.fragment_id) .map_err(|e| SchedulerError::Internal(anyhow!(e))) } @@ -682,11 +683,11 @@ impl StageRunner { fn get_table_dml_vnode_mapping( &self, table_id: &TableId, - ) -> SchedulerResult { + ) -> SchedulerResult { let guard = self.catalog_reader.read_guard(); let table = guard - .get_table_by_id(table_id) + .get_any_table_by_id(table_id) .map_err(|e| SchedulerError::Internal(anyhow!(e)))?; let fragment_id = match table.dml_fragment_id.as_ref() { @@ -711,11 +712,11 @@ impl StageRunner { if let Some(table_id) = dml_table_id { let vnode_mapping = self.get_table_dml_vnode_mapping(&table_id)?; - let parallel_unit_ids = vnode_mapping.iter_unique().collect_vec(); + let worker_slot_ids = vnode_mapping.iter_unique().collect_vec(); let candidates = self .worker_node_manager .manager - .get_workers_by_parallel_unit_ids(¶llel_unit_ids)?; + .get_workers_by_worker_slot_ids(&worker_slot_ids)?; if candidates.is_empty() { return Err(BatchError::EmptyWorkerNodes.into()); } @@ -741,17 +742,17 @@ impl StageRunner { .table_id .into(), )?; - let id2pu_vec = self + let id_to_worker_slots = self .worker_node_manager .fragment_mapping(fragment_id)? .iter_unique() .collect_vec(); - let pu = id2pu_vec[task_id as usize]; + let worker_slot_id = id_to_worker_slots[task_id as usize]; let candidates = self .worker_node_manager .manager - .get_workers_by_parallel_unit_ids(&[pu])?; + .get_workers_by_worker_slot_ids(&[worker_slot_id])?; if candidates.is_empty() { return Err(BatchError::EmptyWorkerNodes.into()); } @@ -869,7 +870,7 @@ impl StageRunner { async fn schedule_task( &self, - task_id: TaskIdPb, + task_id: PbTaskId, plan_fragment: PlanFragment, worker: Option, expr_context: ExprContext, @@ -924,7 +925,7 @@ impl StageRunner { task_id: TaskId, partition: Option, identity_id: Rc>, - ) -> PlanNodePb { + ) -> PbPlanNode { // Generate identity let identity = { let identity_type = execution_plan_node.plan_node_type; @@ -946,7 +947,7 @@ impl StageRunner { let exchange_sources = child_stage.all_exchange_sources_for(task_id); match &execution_plan_node.node { - NodeBody::Exchange(exchange_node) => PlanNodePb { + NodeBody::Exchange(exchange_node) => PbPlanNode { children: vec![], identity, node_body: Some(NodeBody::Exchange(ExchangeNode { @@ -955,7 +956,7 @@ impl StageRunner { input_schema: execution_plan_node.schema.clone(), })), }, - NodeBody::MergeSortExchange(sort_merge_exchange_node) => PlanNodePb { + NodeBody::MergeSortExchange(sort_merge_exchange_node) => PbPlanNode { children: vec![], identity, node_body: Some(NodeBody::MergeSortExchange(MergeSortExchangeNode { @@ -981,12 +982,28 @@ impl StageRunner { .expect("PartitionInfo should be TablePartitionInfo"); scan_node.vnode_bitmap = Some(partition.vnode_bitmap); scan_node.scan_ranges = partition.scan_ranges; - PlanNodePb { + PbPlanNode { children: vec![], identity, node_body: Some(NodeBody::RowSeqScan(scan_node)), } } + PlanNodeType::BatchLogSeqScan => { + let node_body = execution_plan_node.node.clone(); + let NodeBody::LogRowSeqScan(mut scan_node) = node_body else { + unreachable!(); + }; + let partition = partition + .expect("no partition info for seq scan") + .into_table() + .expect("PartitionInfo should be TablePartitionInfo"); + scan_node.vnode_bitmap = Some(partition.vnode_bitmap); + PbPlanNode { + children: vec![], + identity, + node_body: Some(NodeBody::LogRowSeqScan(scan_node)), + } + } PlanNodeType::BatchSource | PlanNodeType::BatchKafkaScan | PlanNodeType::BatchIcebergScan => { @@ -1003,7 +1020,7 @@ impl StageRunner { .into_iter() .map(|split| split.encode_to_bytes().into()) .collect_vec(); - PlanNodePb { + PbPlanNode { children: vec![], identity, node_body: Some(NodeBody::Source(source_node)), @@ -1018,7 +1035,7 @@ impl StageRunner { }) .collect(); - PlanNodePb { + PbPlanNode { children, identity, node_body: Some(execution_plan_node.node.clone()), diff --git a/src/frontend/src/scheduler/local.rs b/src/frontend/src/scheduler/local.rs index 7877ab658b792..4484d6283f71e 100644 --- a/src/frontend/src/scheduler/local.rs +++ b/src/frontend/src/scheduler/local.rs @@ -30,7 +30,7 @@ use risingwave_batch::task::{ShutdownToken, TaskId}; use risingwave_batch::worker_manager::worker_node_manager::WorkerNodeSelector; use risingwave_common::array::DataChunk; use risingwave_common::bail; -use risingwave_common::hash::ParallelUnitMapping; +use risingwave_common::hash::WorkerSlotMapping; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_common::util::tracing::{InstrumentStream, TracingContext}; use risingwave_connector::source::SplitMetaData; @@ -38,7 +38,7 @@ use risingwave_pb::batch_plan::exchange_info::DistributionMode; use risingwave_pb::batch_plan::exchange_source::LocalExecutePlan::Plan; use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::{ - ExchangeInfo, ExchangeSource, LocalExecutePlan, PbTaskId, PlanFragment, PlanNode as PlanNodePb, + ExchangeInfo, ExchangeSource, LocalExecutePlan, PbTaskId, PlanFragment, PlanNode as PbPlanNode, TaskOutputId, }; use risingwave_pb::common::WorkerNode; @@ -55,6 +55,7 @@ use crate::scheduler::task_context::FrontendBatchTaskContext; use crate::scheduler::{ReadSnapshot, SchedulerError, SchedulerResult}; use crate::session::{FrontendEnv, SessionImpl}; +// TODO(error-handling): use a concrete error type. pub type LocalQueryStream = ReceiverStream>; pub struct LocalQueryExecution { sql: String, @@ -273,7 +274,7 @@ impl LocalQueryExecution { second_stages: &mut Option>, partition: Option, next_executor_id: Arc, - ) -> SchedulerResult { + ) -> SchedulerResult { let identity = format!( "{:?}-{}", execution_plan_node.plan_node_type, @@ -312,12 +313,12 @@ impl LocalQueryExecution { // Similar to the distributed case (StageRunner::schedule_tasks). // Set `vnode_ranges` of the scan node in `local_execute_plan` of each // `exchange_source`. - let (parallel_unit_ids, vnode_bitmaps): (Vec<_>, Vec<_>) = + let (worker_ids, vnode_bitmaps): (Vec<_>, Vec<_>) = vnode_bitmaps.clone().into_iter().unzip(); let workers = self .worker_node_manager .manager - .get_workers_by_parallel_unit_ids(¶llel_unit_ids)?; + .get_workers_by_worker_slot_ids(&worker_ids)?; for (idx, (worker_node, partition)) in (workers.into_iter().zip_eq_fast(vnode_bitmaps.into_iter())).enumerate() { @@ -342,7 +343,7 @@ impl LocalQueryExecution { let exchange_source = ExchangeSource { task_output_id: Some(TaskOutputId { task_id: Some(PbTaskId { - task_id: idx as u32, + task_id: idx as u64, stage_id: exchange_source_stage_id, query_id: self.query.query_id.id.clone(), }), @@ -388,7 +389,7 @@ impl LocalQueryExecution { let exchange_source = ExchangeSource { task_output_id: Some(TaskOutputId { task_id: Some(PbTaskId { - task_id: id as u32, + task_id: id as u64, stage_id: exchange_source_stage_id, query_id: self.query.query_id.id.clone(), }), @@ -428,7 +429,7 @@ impl LocalQueryExecution { let exchange_source = ExchangeSource { task_output_id: Some(TaskOutputId { task_id: Some(PbTaskId { - task_id: idx as u32, + task_id: idx as u64, stage_id: exchange_source_stage_id, query_id: self.query.query_id.id.clone(), }), @@ -442,7 +443,7 @@ impl LocalQueryExecution { .collect(); } - Ok(PlanNodePb { + Ok(PbPlanNode { // Since all the rest plan is embedded into the exchange node, // there is no children any more. children: vec![], @@ -466,7 +467,27 @@ impl LocalQueryExecution { _ => unreachable!(), } - Ok(PlanNodePb { + Ok(PbPlanNode { + children: vec![], + identity, + node_body: Some(node_body), + }) + } + PlanNodeType::BatchLogSeqScan => { + let mut node_body = execution_plan_node.node.clone(); + match &mut node_body { + NodeBody::LogRowSeqScan(ref mut scan_node) => { + if let Some(partition) = partition { + let partition = partition + .into_table() + .expect("PartitionInfo should be TablePartitionInfo here"); + scan_node.vnode_bitmap = Some(partition.vnode_bitmap); + } + } + _ => unreachable!(), + } + + Ok(PbPlanNode { children: vec![], identity, node_body: Some(node_body), @@ -491,7 +512,7 @@ impl LocalQueryExecution { _ => unreachable!(), } - Ok(PlanNodePb { + Ok(PbPlanNode { children: vec![], identity, node_body: Some(node_body), @@ -510,7 +531,8 @@ impl LocalQueryExecution { )?; // TODO: should we use `pb::ParallelUnitMapping` here? - node.inner_side_vnode_mapping = mapping.to_expanded(); + node.inner_side_vnode_mapping = + mapping.to_expanded().into_iter().map(u64::from).collect(); node.worker_nodes = self.worker_node_manager.manager.list_worker_nodes(); } _ => unreachable!(), @@ -523,7 +545,7 @@ impl LocalQueryExecution { next_executor_id, )?; - Ok(PlanNodePb { + Ok(PbPlanNode { children: vec![left_child], identity, node_body: Some(node_body), @@ -541,9 +563,9 @@ impl LocalQueryExecution { next_executor_id.clone(), ) }) - .collect::>>()?; + .collect::>>()?; - Ok(PlanNodePb { + Ok(PbPlanNode { children, identity, node_body: Some(execution_plan_node.node.clone()), @@ -556,7 +578,7 @@ impl LocalQueryExecution { fn get_fragment_id(&self, table_id: &TableId) -> SchedulerResult { let reader = self.front_env.catalog_reader().read_guard(); reader - .get_table_by_id(table_id) + .get_any_table_by_id(table_id) .map(|table| table.fragment_id) .map_err(|e| SchedulerError::Internal(anyhow!(e))) } @@ -565,11 +587,11 @@ impl LocalQueryExecution { fn get_table_dml_vnode_mapping( &self, table_id: &TableId, - ) -> SchedulerResult { + ) -> SchedulerResult { let guard = self.front_env.catalog_reader().read_guard(); let table = guard - .get_table_by_id(table_id) + .get_any_table_by_id(table_id) .map_err(|e| SchedulerError::Internal(anyhow!(e)))?; let fragment_id = match table.dml_fragment_id.as_ref() { @@ -589,11 +611,11 @@ impl LocalQueryExecution { // dml should use streaming vnode mapping let vnode_mapping = self.get_table_dml_vnode_mapping(table_id)?; let worker_node = { - let parallel_unit_ids = vnode_mapping.iter_unique().collect_vec(); + let worker_ids = vnode_mapping.iter_unique().collect_vec(); let candidates = self .worker_node_manager .manager - .get_workers_by_parallel_unit_ids(¶llel_unit_ids)?; + .get_workers_by_worker_slot_ids(&worker_ids)?; if candidates.is_empty() { return Err(BatchError::EmptyWorkerNodes.into()); } diff --git a/src/frontend/src/scheduler/plan_fragmenter.rs b/src/frontend/src/scheduler/plan_fragmenter.rs index f5ed1b1205b69..699a84fdc3e3a 100644 --- a/src/frontend/src/scheduler/plan_fragmenter.rs +++ b/src/frontend/src/scheduler/plan_fragmenter.rs @@ -27,10 +27,10 @@ use pgwire::pg_server::SessionId; use risingwave_batch::error::BatchError; use risingwave_batch::worker_manager::worker_node_manager::WorkerNodeSelector; use risingwave_common::bail; -use risingwave_common::buffer::{Bitmap, BitmapBuilder}; +use risingwave_common::bitmap::{Bitmap, BitmapBuilder}; use risingwave_common::catalog::TableDesc; use risingwave_common::hash::table_distribution::TableDistribution; -use risingwave_common::hash::{ParallelUnitId, ParallelUnitMapping, VirtualNode}; +use risingwave_common::hash::{VirtualNode, WorkerSlotId, WorkerSlotMapping}; use risingwave_common::util::scan_range::ScanRange; use risingwave_connector::source::filesystem::opendal_source::opendal_enumerator::OpendalEnumerator; use risingwave_connector::source::filesystem::opendal_source::{OpendalGcs, OpendalS3}; @@ -43,7 +43,7 @@ use risingwave_connector::source::{ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::{ExchangeInfo, ScanRange as ScanRangeProto}; use risingwave_pb::common::Buffer; -use risingwave_pb::plan_common::Field as FieldPb; +use risingwave_pb::plan_common::Field as PbField; use risingwave_sqlparser::ast::AsOf; use serde::ser::SerializeStruct; use serde::Serialize; @@ -75,10 +75,10 @@ impl std::fmt::Display for QueryId { pub type StageId = u32; // Root stage always has only one task. -pub const ROOT_TASK_ID: u32 = 0; +pub const ROOT_TASK_ID: u64 = 0; // Root task has only one output. -pub const ROOT_TASK_OUTPUT_ID: u32 = 0; -pub type TaskId = u32; +pub const ROOT_TASK_OUTPUT_ID: u64 = 0; +pub type TaskId = u64; /// Generated by [`BatchPlanFragmenter`] and used in query execution graph. #[derive(Clone, Debug)] @@ -86,7 +86,7 @@ pub struct ExecutionPlanNode { pub plan_node_id: PlanNodeId, pub plan_node_type: PlanNodeType, pub node: NodeBody, - pub schema: Vec, + pub schema: Vec, pub children: Vec>, @@ -392,12 +392,12 @@ pub struct TableScanInfo { /// full vnode bitmap, since we need to know where to schedule the singleton scan task. /// /// `None` iff the table is a system table. - partitions: Option>, + partitions: Option>, } impl TableScanInfo { /// For normal tables, `partitions` should always be `Some`. - pub fn new(name: String, partitions: HashMap) -> Self { + pub fn new(name: String, partitions: HashMap) -> Self { Self { name, partitions: Some(partitions), @@ -416,7 +416,7 @@ impl TableScanInfo { self.name.as_ref() } - pub fn partitions(&self) -> Option<&HashMap> { + pub fn partitions(&self) -> Option<&HashMap> { self.partitions.as_ref() } } @@ -1064,31 +1064,39 @@ impl BatchPlanFragmenter { /// If there are multiple scan nodes in this stage, they must have the same distribution, but /// maybe different vnodes partition. We just use the same partition for all the scan nodes. fn collect_stage_table_scan(&self, node: PlanRef) -> SchedulerResult> { - if node.node_type() == PlanNodeType::BatchExchange { - // Do not visit next stage. - return Ok(None); - } - if let Some(scan_node) = node.as_batch_sys_seq_scan() { - let name = scan_node.core().table_name.to_owned(); - return Ok(Some(TableScanInfo::system_table(name))); - } - - if let Some(scan_node) = node.as_batch_seq_scan() { - let name = scan_node.core().table_name.to_owned(); - let table_desc = &*scan_node.core().table_desc; + let build_table_scan_info = |name, table_desc: &TableDesc, scan_range| { let table_catalog = self .catalog_reader .read_guard() - .get_table_by_id(&table_desc.table_id) + .get_any_table_by_id(&table_desc.table_id) .cloned() .map_err(RwError::from)?; let vnode_mapping = self .worker_node_manager .fragment_mapping(table_catalog.fragment_id)?; - let partitions = - derive_partitions(scan_node.scan_ranges(), table_desc, &vnode_mapping)?; + let partitions = derive_partitions(scan_range, table_desc, &vnode_mapping)?; let info = TableScanInfo::new(name, partitions); Ok(Some(info)) + }; + if node.node_type() == PlanNodeType::BatchExchange { + // Do not visit next stage. + return Ok(None); + } + if let Some(scan_node) = node.as_batch_sys_seq_scan() { + let name = scan_node.core().table_name.to_owned(); + Ok(Some(TableScanInfo::system_table(name))) + } else if let Some(scan_node) = node.as_batch_log_seq_scan() { + build_table_scan_info( + scan_node.core().table_name.to_owned(), + &scan_node.core().table_desc, + &[], + ) + } else if let Some(scan_node) = node.as_batch_seq_scan() { + build_table_scan_info( + scan_node.core().table_name.to_owned(), + &scan_node.core().table_desc, + scan_node.scan_ranges(), + ) } else { node.inputs() .into_iter() @@ -1128,7 +1136,7 @@ impl BatchPlanFragmenter { let table_catalog = self .catalog_reader .read_guard() - .get_table_by_id(&table_desc.table_id) + .get_any_table_by_id(&table_desc.table_id) .cloned() .map_err(RwError::from)?; let vnode_mapping = self @@ -1150,10 +1158,10 @@ impl BatchPlanFragmenter { fn derive_partitions( scan_ranges: &[ScanRange], table_desc: &TableDesc, - vnode_mapping: &ParallelUnitMapping, -) -> SchedulerResult> { + vnode_mapping: &WorkerSlotMapping, +) -> SchedulerResult> { let num_vnodes = vnode_mapping.len(); - let mut partitions: HashMap)> = HashMap::new(); + let mut partitions: HashMap)> = HashMap::new(); if scan_ranges.is_empty() { return Ok(vnode_mapping @@ -1182,9 +1190,9 @@ fn derive_partitions( None => { // put this scan_range to all partitions vnode_mapping.to_bitmaps().into_iter().for_each( - |(parallel_unit_id, vnode_bitmap)| { + |(worker_slot_id, vnode_bitmap)| { let (bitmap, scan_ranges) = partitions - .entry(parallel_unit_id) + .entry(worker_slot_id) .or_insert_with(|| (BitmapBuilder::zeroed(num_vnodes), vec![])); vnode_bitmap .iter() @@ -1196,9 +1204,9 @@ fn derive_partitions( } // scan a single partition Some(vnode) => { - let parallel_unit_id = vnode_mapping[vnode]; + let worker_slot_id = vnode_mapping[vnode]; let (bitmap, scan_ranges) = partitions - .entry(parallel_unit_id) + .entry(worker_slot_id) .or_insert_with(|| (BitmapBuilder::zeroed(num_vnodes), vec![])); bitmap.set(vnode.to_index(), true); scan_ranges.push(scan_range.to_protobuf()); diff --git a/src/frontend/src/scheduler/task_context.rs b/src/frontend/src/scheduler/task_context.rs index 35f20a6daeffd..fff154d8a196e 100644 --- a/src/frontend/src/scheduler/task_context.rs +++ b/src/frontend/src/scheduler/task_context.rs @@ -94,12 +94,8 @@ impl BatchTaskContext for FrontendBatchTaskContext { self.session.env().source_metrics() } - fn store_mem_usage(&self, _val: usize) { - todo!() - } - - fn mem_usage(&self) -> usize { - todo!() + fn spill_metrics(&self) -> Arc { + self.session.env().spill_metrics() } fn create_executor_mem_context(&self, _executor_id: &str) -> MemoryContext { diff --git a/src/frontend/src/session.rs b/src/frontend/src/session.rs index 7ff790748a761..2148656bee00b 100644 --- a/src/frontend/src/session.rs +++ b/src/frontend/src/session.rs @@ -20,6 +20,7 @@ use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; +use anyhow::anyhow; use bytes::Bytes; use either::Either; use parking_lot::{Mutex, RwLock, RwLockReadGuard}; @@ -34,6 +35,8 @@ use pgwire::pg_server::{ }; use pgwire::types::{Format, FormatIterator}; use rand::RngCore; +use risingwave_batch::monitor::{BatchSpillMetrics, GLOBAL_BATCH_SPILL_METRICS}; +use risingwave_batch::spill::spill_op::SpillOp; use risingwave_batch::task::{ShutdownSender, ShutdownToken}; use risingwave_batch::worker_manager::worker_node_manager::{ WorkerNodeManager, WorkerNodeManagerRef, @@ -85,7 +88,7 @@ use crate::catalog::connection_catalog::ConnectionCatalog; use crate::catalog::root_catalog::Catalog; use crate::catalog::subscription_catalog::SubscriptionCatalog; use crate::catalog::{ - check_schema_writable, CatalogError, DatabaseId, OwnedByUserCatalog, SchemaId, + check_schema_writable, CatalogError, DatabaseId, OwnedByUserCatalog, SchemaId, TableId, }; use crate::error::{ErrorCode, Result, RwError}; use crate::handler::describe::infer_describe; @@ -111,7 +114,7 @@ use crate::user::user_authentication::md5_hash_with_salt; use crate::user::user_manager::UserInfoManager; use crate::user::user_service::{UserInfoReader, UserInfoWriter, UserInfoWriterImpl}; use crate::user::UserId; -use crate::{FrontendOpts, PgResponseStream}; +use crate::{FrontendOpts, PgResponseStream, TableCatalog}; pub(crate) mod current; pub(crate) mod cursor_manager; @@ -145,6 +148,9 @@ pub struct FrontendEnv { source_metrics: Arc, + /// Batch spill metrics + spill_metrics: Arc, + batch_config: BatchConfig, meta_config: MetaConfig, streaming_config: StreamingConfig, @@ -164,6 +170,9 @@ pub struct FrontendEnv { /// Session map identified by `(process_id, secret_key)` type SessionMapRef = Arc>>>; +/// The proportion of frontend memory used for batch processing. +const FRONTEND_BATCH_MEMORY_PROPORTION: f64 = 0.5; + impl FrontendEnv { pub fn mock() -> Self { use crate::test_utils::{MockCatalogWriter, MockFrontendMetaClient, MockUserInfoWriter}; @@ -221,6 +230,7 @@ impl FrontendEnv { meta_config: MetaConfig::default(), streaming_config: StreamingConfig::default(), source_metrics: Arc::new(SourceMetrics::default()), + spill_metrics: BatchSpillMetrics::for_test(), creating_streaming_job_tracker: Arc::new(creating_streaming_tracker), compute_runtime, mem_context: MemoryContext::none(), @@ -333,15 +343,12 @@ impl FrontendEnv { let frontend_metrics = Arc::new(GLOBAL_FRONTEND_METRICS.clone()); let source_metrics = Arc::new(GLOBAL_SOURCE_METRICS.clone()); + let spill_metrics = Arc::new(GLOBAL_BATCH_SPILL_METRICS.clone()); if config.server.metrics_level > MetricLevel::Disabled { MetricsManager::boot_metrics_service(opts.prometheus_listener_addr.clone()); } - let mem_context = risingwave_common::memory::MemoryContext::root( - frontend_metrics.batch_total_mem.clone(), - ); - let health_srv = HealthServiceImpl::new(); let host = opts.health_check_listener_addr.clone(); @@ -402,12 +409,23 @@ impl FrontendEnv { }); join_handles.push(join_handle); + // Clean up the spill directory. + #[cfg(not(madsim))] + SpillOp::clean_spill_directory() + .await + .map_err(|err| anyhow!(err))?; + let total_memory_bytes = resource_util::memory::system_memory_available_bytes(); let heap_profiler = HeapProfiler::new(total_memory_bytes, config.server.heap_profiling.clone()); // Run a background heap profiler heap_profiler.start(); + let mem_context = risingwave_common::memory::MemoryContext::root( + frontend_metrics.batch_total_mem.clone(), + (total_memory_bytes as f64 * FRONTEND_BATCH_MEMORY_PROPORTION) as u64, + ); + Ok(( Self { catalog_reader, @@ -423,6 +441,7 @@ impl FrontendEnv { server_addr: frontend_address, client_pool, frontend_metrics, + spill_metrics, sessions_map, batch_config, meta_config, @@ -519,6 +538,10 @@ impl FrontendEnv { self.source_metrics.clone() } + pub fn spill_metrics(&self) -> Arc { + self.spill_metrics.clone() + } + pub fn creating_streaming_job_tracker(&self) -> &StreamingJobTrackerRef { &self.creating_streaming_job_tracker } @@ -803,6 +826,26 @@ impl SessionImpl { } } + pub fn check_secret_name_duplicated(&self, name: ObjectName) -> Result<()> { + let db_name = self.database(); + let catalog_reader = self.env().catalog_reader().read_guard(); + let (schema_name, secret_name) = { + let (schema_name, secret_name) = Binder::resolve_schema_qualified_name(db_name, name)?; + let search_path = self.config().search_path(); + let user_name = &self.auth_context().user_name; + let schema_name = match schema_name { + Some(schema_name) => schema_name, + None => catalog_reader + .first_valid_schema(db_name, &search_path, user_name)? + .name(), + }; + (schema_name, secret_name) + }; + catalog_reader + .check_secret_name_duplicated(db_name, &schema_name, &secret_name) + .map_err(RwError::from) + } + pub fn check_connection_name_duplicated(&self, name: ObjectName) -> Result<()> { let db_name = self.database(); let catalog_reader = self.env().catalog_reader().read_guard(); @@ -879,6 +922,27 @@ impl SessionImpl { Ok(connection.clone()) } + pub fn get_subscription_by_schema_id_name( + &self, + schema_id: SchemaId, + subscription_name: &str, + ) -> Result> { + let db_name = self.database(); + + let catalog_reader = self.env().catalog_reader().read_guard(); + let db_id = catalog_reader.get_database_by_name(db_name)?.id(); + let schema = catalog_reader.get_schema_by_id(&db_id, &schema_id)?; + let subscription = schema + .get_subscription_by_name(subscription_name) + .ok_or_else(|| { + RwError::from(ErrorCode::ItemNotFound(format!( + "subscription {} not found", + subscription_name + ))) + })?; + Ok(subscription.clone()) + } + pub fn get_subscription_by_name( &self, schema_name: Option, @@ -905,6 +969,43 @@ impl SessionImpl { Ok(subscription.clone()) } + pub fn get_table_by_id(&self, table_id: &TableId) -> Result> { + let catalog_reader = self.env().catalog_reader().read_guard(); + Ok(catalog_reader.get_any_table_by_id(table_id)?.clone()) + } + + pub fn get_table_by_name( + &self, + table_name: &str, + db_id: u32, + schema_id: u32, + ) -> Result> { + let catalog_reader = self.env().catalog_reader().read_guard(); + let table = catalog_reader + .get_schema_by_id(&DatabaseId::from(db_id), &SchemaId::from(schema_id))? + .get_created_table_by_name(table_name) + .ok_or_else(|| { + Error::new( + ErrorKind::InvalidInput, + format!("table \"{}\" does not exist", table_name), + ) + })?; + Ok(table.clone()) + } + + pub async fn list_change_log_epochs( + &self, + table_id: u32, + min_epoch: u64, + max_count: u32, + ) -> Result> { + Ok(self + .env + .meta_client() + .list_change_log_epochs(table_id, min_epoch, max_count) + .await?) + } + pub fn clear_cancel_query_flag(&self) { let mut flag = self.current_query_cancel_flag.lock(); *flag = None; @@ -1103,10 +1204,18 @@ impl SessionManager for SessionManagerImpl { fn end_session(&self, session: &Self::Session) { self.delete_session(&session.session_id()); } + + async fn shutdown(&self) { + // Clean up the session map. + self.env.sessions_map().write().clear(); + // Unregister from the meta service. + self.env.meta_client().try_unregister().await; + } } impl SessionManagerImpl { pub async fn new(opts: FrontendOpts) -> Result { + // TODO(shutdown): only save join handles that **need** to be shutdown let (env, join_handles, shutdown_senders) = FrontendEnv::init(opts).await?; Ok(Self { env, @@ -1116,6 +1225,10 @@ impl SessionManagerImpl { }) } + pub fn env(&self) -> &FrontendEnv { + &self.env + } + fn insert_session(&self, session: Arc) { let active_sessions = { let mut write_guard = self.env.sessions_map.write(); diff --git a/src/frontend/src/session/cursor_manager.rs b/src/frontend/src/session/cursor_manager.rs index 214dac1a2b5a2..bcd1aa11ec749 100644 --- a/src/frontend/src/session/cursor_manager.rs +++ b/src/frontend/src/session/cursor_manager.rs @@ -12,34 +12,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::ops::Index; use core::time::Duration; use std::collections::{HashMap, VecDeque}; +use std::rc::Rc; use std::sync::Arc; use std::time::Instant; use bytes::Bytes; +use fixedbitset::FixedBitSet; use futures::StreamExt; -use itertools::Itertools; use pgwire::pg_field_descriptor::PgFieldDescriptor; +use pgwire::pg_response::StatementType; use pgwire::types::Row; +use risingwave_common::session_config::QueryMode; use risingwave_common::types::DataType; use risingwave_sqlparser::ast::{Ident, ObjectName, Statement}; +use super::SessionImpl; use crate::catalog::subscription_catalog::SubscriptionCatalog; -use crate::error::{ErrorCode, Result, RwError}; -use crate::handler::declare_cursor::create_stream_for_cursor; -use crate::handler::util::{ - convert_epoch_to_logstore_i64, convert_logstore_i64_to_unix_millis, - gen_query_from_logstore_ge_rw_timestamp, gen_query_from_table_name, -}; +use crate::catalog::TableId; +use crate::error::{ErrorCode, Result}; +use crate::handler::declare_cursor::create_stream_for_cursor_stmt; +use crate::handler::query::{create_stream, gen_batch_plan_fragmenter, BatchQueryPlanResult}; +use crate::handler::util::{convert_logstore_u64_to_unix_millis, gen_query_from_table_name}; use crate::handler::HandlerArgs; -use crate::PgResponseStream; - -pub const KV_LOG_STORE_EPOCH: &str = "kv_log_store_epoch"; -const KV_LOG_STORE_ROW_OP: &str = "kv_log_store_row_op"; -pub const KV_LOG_STORE_SEQ_ID: &str = "kv_log_store_seq_id"; -pub const KV_LOG_STORE_VNODE: &str = "kv_log_store_vnode"; +use crate::optimizer::plan_node::{generic, BatchLogSeqScan}; +use crate::optimizer::property::{Order, RequiredDist}; +use crate::optimizer::PlanRoot; +use crate::{OptimizerContext, OptimizerContextRef, PgResponseStream, PlanRef, TableCatalog}; pub enum Cursor { Subscription(SubscriptionCursor), @@ -78,9 +78,7 @@ impl QueryCursor { let rows = self.row_stream.next().await; let rows = match rows { None => return Ok(None), - Some(row) => { - row.map_err(|err| RwError::from(ErrorCode::InternalError(format!("{}", err))))? - } + Some(row) => row?, }; self.remaining_rows = rows.into_iter().collect(); } @@ -106,10 +104,10 @@ impl QueryCursor { enum State { InitLogStoreQuery { // The rw_timestamp used to initiate the query to read from subscription logstore. - seek_timestamp: i64, + seek_timestamp: u64, // If specified, the expected_timestamp must be an exact match for the next rw_timestamp. - expected_timestamp: Option, + expected_timestamp: Option, }, Fetch { // Whether the query is reading from snapshot @@ -118,7 +116,7 @@ enum State { from_snapshot: bool, // The rw_timestamp used to initiate the query to read from subscription logstore. - rw_timestamp: i64, + rw_timestamp: u64, // The row stream to from the batch query read. // It is returned from the batch execution. @@ -130,6 +128,8 @@ enum State { // A cache to store the remaining rows from the row stream. remaining_rows: VecDeque, + + expected_timestamp: Option, }, Invalid, } @@ -137,6 +137,7 @@ enum State { pub struct SubscriptionCursor { cursor_name: String, subscription: Arc, + dependent_table_id: TableId, cursor_need_drop_time: Instant, state: State, } @@ -144,8 +145,9 @@ pub struct SubscriptionCursor { impl SubscriptionCursor { pub async fn new( cursor_name: String, - start_timestamp: Option, + start_timestamp: Option, subscription: Arc, + dependent_table_id: TableId, handle_args: &HandlerArgs, ) -> Result { let state = if let Some(start_timestamp) = start_timestamp { @@ -159,7 +161,7 @@ impl SubscriptionCursor { // // TODO: is this the right behavior? Should we delay the query stream initiation till the first fetch? let (row_stream, pg_descs) = - Self::initiate_query(None, &subscription, handle_args.clone()).await?; + Self::initiate_query(None, &dependent_table_id, handle_args.clone()).await?; let pinned_epoch = handle_args .session .get_pinned_snapshot() @@ -173,7 +175,7 @@ impl SubscriptionCursor { ) })? .0; - let start_timestamp = convert_epoch_to_logstore_i64(pinned_epoch); + let start_timestamp = pinned_epoch; State::Fetch { from_snapshot: true, @@ -181,22 +183,25 @@ impl SubscriptionCursor { row_stream, pg_descs, remaining_rows: VecDeque::new(), + expected_timestamp: None, } }; let cursor_need_drop_time = - Instant::now() + Duration::from_secs(subscription.get_retention_seconds()?); + Instant::now() + Duration::from_secs(subscription.retention_seconds); Ok(Self { cursor_name, subscription, + dependent_table_id, cursor_need_drop_time, state, }) } - pub async fn next_row( + async fn next_row( &mut self, - handle_args: HandlerArgs, + handle_args: &HandlerArgs, + expected_pg_descs: &Vec, ) -> Result<(Option, Vec)> { loop { match &mut self.state { @@ -207,64 +212,48 @@ impl SubscriptionCursor { let from_snapshot = false; // Initiate a new batch query to continue fetching - let (mut row_stream, pg_descs) = Self::initiate_query( - Some(*seek_timestamp), - &self.subscription, + match Self::get_next_rw_timestamp( + *seek_timestamp, + &self.dependent_table_id, + *expected_timestamp, handle_args.clone(), + &self.subscription, ) - .await?; - self.cursor_need_drop_time = Instant::now() - + Duration::from_secs(self.subscription.get_retention_seconds()?); - - // Try refill remaining rows - let mut remaining_rows = VecDeque::new(); - Self::try_refill_remaining_rows(&mut row_stream, &mut remaining_rows).await?; - - // Get the rw_timestamp in the first row returned by the query if any. - // new_row_rw_timestamp == None means the query returns empty result. - let new_row_rw_timestamp: Option = remaining_rows.front().map(|row| { - std::str::from_utf8(row.index(0).as_ref().unwrap()) - .unwrap() - .parse() - .unwrap() - }); - - // Check expected_timestamp against the rw_timestamp of the first row. - // Return an error if there is no new row or there is a mismatch. - if let Some(expected_timestamp) = expected_timestamp { - let expected_timestamp = *expected_timestamp; - if new_row_rw_timestamp.is_none() - || new_row_rw_timestamp.unwrap() != expected_timestamp - { - // Transition to Invalid state and return and error - self.state = State::Invalid; - return Err(ErrorCode::CatalogError( - format!( - " No data found for rw_timestamp {:?}, data may have been recycled, please recreate cursor", - convert_logstore_i64_to_unix_millis(expected_timestamp) - ) - .into(), + .await + { + Ok((Some(rw_timestamp), expected_timestamp)) => { + let (mut row_stream, pg_descs) = Self::initiate_query( + Some(rw_timestamp), + &self.dependent_table_id, + handle_args.clone(), ) - .into()); + .await?; + self.cursor_need_drop_time = Instant::now() + + Duration::from_secs(self.subscription.retention_seconds); + let mut remaining_rows = VecDeque::new(); + Self::try_refill_remaining_rows(&mut row_stream, &mut remaining_rows) + .await?; + // Transition to the Fetch state + self.state = State::Fetch { + from_snapshot, + rw_timestamp, + row_stream, + pg_descs: pg_descs.clone(), + remaining_rows, + expected_timestamp, + }; + if (!expected_pg_descs.is_empty()) && expected_pg_descs.ne(&pg_descs) { + // If the user alters the table upstream of the sub, there will be different descs here. + // So we should output data for different descs in two separate batches + return Ok((None, vec![])); + } + } + Ok((None, _)) => return Ok((None, vec![])), + Err(e) => { + self.state = State::Invalid; + return Err(e); } } - - // Return None if no data is found for the rw_timestamp in logstore. - // This happens when reaching EOF of logstore. This check cannot be moved before the - // expected_timestamp check to ensure that an error is returned on empty result when - // expected_timstamp is set. - if new_row_rw_timestamp.is_none() { - return Ok((None, pg_descs)); - } - - // Transition to the Fetch state - self.state = State::Fetch { - from_snapshot, - rw_timestamp: new_row_rw_timestamp.unwrap(), - row_stream, - pg_descs, - remaining_rows, - }; } State::Fetch { from_snapshot, @@ -272,6 +261,7 @@ impl SubscriptionCursor { row_stream, pg_descs, remaining_rows, + expected_timestamp, } => { let from_snapshot = *from_snapshot; let rw_timestamp = *rw_timestamp; @@ -283,46 +273,29 @@ impl SubscriptionCursor { // 1. Fetch the next row let new_row = row.take(); if from_snapshot { - // 1a. The rw_timestamp in the table is all the same, so don't need to check. return Ok(( - Some(Row::new(Self::build_row_with_snapshot(new_row))), + Some(Row::new(Self::build_row(new_row, None)?)), pg_descs.clone(), )); - } - - let new_row_rw_timestamp: i64 = new_row - .get(0) - .unwrap() - .as_ref() - .map(|bytes| std::str::from_utf8(bytes).unwrap().parse().unwrap()) - .unwrap(); - - if new_row_rw_timestamp != rw_timestamp { - // 1b. Find the next rw_timestamp. - // Initiate a new batch query to avoid query timeout and pinning version for too long. - // expected_timestamp shouold be set to ensure there is no data missing in the next query. - self.state = State::InitLogStoreQuery { - seek_timestamp: new_row_rw_timestamp, - expected_timestamp: Some(new_row_rw_timestamp), - }; } else { - // 1c. The rw_timestamp of this row is equal to self.rw_timestamp, return row return Ok(( - Some(Row::new(Self::build_row_with_logstore( - new_row, - rw_timestamp, - )?)), + Some(Row::new(Self::build_row(new_row, Some(rw_timestamp))?)), pg_descs.clone(), )); } } else { // 2. Reach EOF for the current query. - // Initiate a new batch query using the rw_timestamp + 1. - // expected_timestamp don't need to be set as the next rw_timestamp is unknown. - self.state = State::InitLogStoreQuery { - seek_timestamp: rw_timestamp + 1, - expected_timestamp: None, - }; + if let Some(expected_timestamp) = expected_timestamp { + self.state = State::InitLogStoreQuery { + seek_timestamp: *expected_timestamp, + expected_timestamp: Some(*expected_timestamp), + }; + } else { + self.state = State::InitLogStoreQuery { + seek_timestamp: rw_timestamp + 1, + expected_timestamp: None, + }; + } } } State::Invalid => { @@ -348,41 +321,88 @@ impl SubscriptionCursor { ) .into()); } - // `FETCH NEXT` is equivalent to `FETCH 1`. - if count != 1 { - Err(crate::error::ErrorCode::InternalError( - "FETCH count with subscription is not supported".to_string(), - ) - .into()) - } else { - let (row, pg_descs) = self.next_row(handle_args).await?; - if let Some(row) = row { - Ok((vec![row], pg_descs)) - } else { - Ok((vec![], pg_descs)) + + let mut ans = Vec::with_capacity(std::cmp::min(100, count) as usize); + let mut cur = 0; + let mut pg_descs_ans = vec![]; + while cur < count { + let (row, descs_ans) = self.next_row(&handle_args, &pg_descs_ans).await?; + match row { + Some(row) => { + pg_descs_ans = descs_ans; + cur += 1; + ans.push(row); + } + None => { + break; + } } } + + Ok((ans, pg_descs_ans)) + } + + async fn get_next_rw_timestamp( + seek_timestamp: u64, + table_id: &TableId, + expected_timestamp: Option, + handle_args: HandlerArgs, + dependent_subscription: &SubscriptionCatalog, + ) -> Result<(Option, Option)> { + let session = handle_args.session; + // Test subscription existence + session.get_subscription_by_schema_id_name( + dependent_subscription.schema_id, + &dependent_subscription.name, + )?; + + // The epoch here must be pulled every time, otherwise there will be cache consistency issues + let new_epochs = session + .list_change_log_epochs(table_id.table_id(), seek_timestamp, 2) + .await?; + if let Some(expected_timestamp) = expected_timestamp + && (new_epochs.is_empty() || &expected_timestamp != new_epochs.first().unwrap()) + { + return Err(ErrorCode::CatalogError( + format!( + " No data found for rw_timestamp {:?}, data may have been recycled, please recreate cursor", + convert_logstore_u64_to_unix_millis(expected_timestamp) + ) + .into(), + ) + .into()); + } + Ok((new_epochs.get(0).cloned(), new_epochs.get(1).cloned())) } async fn initiate_query( - rw_timestamp: Option, - subscription: &SubscriptionCatalog, + rw_timestamp: Option, + dependent_table_id: &TableId, handle_args: HandlerArgs, ) -> Result<(PgResponseStream, Vec)> { - let query_stmt = if let Some(rw_timestamp) = rw_timestamp { - Statement::Query(Box::new(gen_query_from_logstore_ge_rw_timestamp( - &subscription.get_log_store_name(), - rw_timestamp, - ))) + let session = handle_args.clone().session; + let table_catalog = session.get_table_by_id(dependent_table_id)?; + let (row_stream, pg_descs) = if let Some(rw_timestamp) = rw_timestamp { + let context = OptimizerContext::from_handler_args(handle_args); + let plan_fragmenter_result = gen_batch_plan_fragmenter( + &session, + Self::create_batch_plan_for_cursor( + &table_catalog, + &session, + context.into(), + rw_timestamp, + rw_timestamp, + )?, + )?; + create_stream(session, plan_fragmenter_result, vec![]).await? } else { - let subscription_from_table_name = ObjectName(vec![Ident::from( - subscription.subscription_from_name.as_ref(), - )]); - Statement::Query(Box::new(gen_query_from_table_name( + let subscription_from_table_name = + ObjectName(vec![Ident::from(table_catalog.name.as_ref())]); + let query_stmt = Statement::Query(Box::new(gen_query_from_table_name( subscription_from_table_name, - ))) + ))); + create_stream_for_cursor_stmt(handle_args, query_stmt).await? }; - let (row_stream, pg_descs) = create_stream_for_cursor(handle_args, query_stmt).await?; Ok(( row_stream, Self::build_desc(pg_descs, rw_timestamp.is_none()), @@ -396,66 +416,97 @@ impl SubscriptionCursor { if remaining_rows.is_empty() && let Some(row_set) = row_stream.next().await { - remaining_rows.extend(row_set.map_err(|e| { - ErrorCode::InternalError(format!("Cursor get next chunk error {:?}", e.to_string())) - })?); + remaining_rows.extend(row_set?); } Ok(()) } - pub fn build_row_with_snapshot(row: Vec>) -> Vec> { - let mut new_row = vec![None, Some(Bytes::from(1i16.to_string()))]; - new_row.extend(row); - new_row - } - - pub fn build_row_with_logstore( + pub fn build_row( mut row: Vec>, - rw_timestamp: i64, + rw_timestamp: Option, ) -> Result>> { - let mut new_row = vec![Some(Bytes::from( - convert_logstore_i64_to_unix_millis(rw_timestamp).to_string(), - ))]; - // need remove kv_log_store_epoch - new_row.extend(row.drain(1..row.len()).collect_vec()); - Ok(new_row) + let new_row = if let Some(rw_timestamp) = rw_timestamp { + vec![Some(Bytes::from( + convert_logstore_u64_to_unix_millis(rw_timestamp).to_string(), + ))] + } else { + vec![Some(Bytes::from(1i16.to_string())), None] + }; + row.extend(new_row); + Ok(row) } pub fn build_desc( mut descs: Vec, from_snapshot: bool, ) -> Vec { - let mut new_descs = vec![ - PgFieldDescriptor::new( - "rw_timestamp".to_owned(), - DataType::Int64.to_oid(), - DataType::Int64.type_len(), - ), - PgFieldDescriptor::new( + if from_snapshot { + descs.push(PgFieldDescriptor::new( "op".to_owned(), DataType::Int16.to_oid(), DataType::Int16.type_len(), - ), - ]; - // need remove kv_log_store_epoch and kv_log_store_row_op - if from_snapshot { - new_descs.extend(descs) - } else { - assert_eq!( - descs.get(0).unwrap().get_name(), - KV_LOG_STORE_EPOCH, - "Cursor query logstore: first column must be {}", - KV_LOG_STORE_EPOCH - ); - assert_eq!( - descs.get(1).unwrap().get_name(), - KV_LOG_STORE_ROW_OP, - "Cursor query logstore: first column must be {}", - KV_LOG_STORE_ROW_OP - ); - new_descs.extend(descs.drain(2..descs.len())); + )); } - new_descs + descs.push(PgFieldDescriptor::new( + "rw_timestamp".to_owned(), + DataType::Int64.to_oid(), + DataType::Int64.type_len(), + )); + descs + } + + pub fn create_batch_plan_for_cursor( + table_catalog: &TableCatalog, + session: &SessionImpl, + context: OptimizerContextRef, + old_epoch: u64, + new_epoch: u64, + ) -> Result { + let out_col_idx = table_catalog + .columns + .iter() + .enumerate() + .filter(|(_, v)| !v.is_hidden) + .map(|(i, _)| i) + .collect::>(); + let core = generic::LogScan::new( + table_catalog.name.clone(), + out_col_idx, + Rc::new(table_catalog.table_desc()), + context, + old_epoch, + new_epoch, + ); + let batch_log_seq_scan = BatchLogSeqScan::new(core); + let schema = batch_log_seq_scan + .core() + .schema_without_table_name() + .clone(); + let out_fields = FixedBitSet::from_iter(0..schema.len()); + let out_names = batch_log_seq_scan.core().column_names(); + // Here we just need a plan_root to call the method, only out_fields and out_names will be used + let plan_root = PlanRoot::new_with_batch_plan( + PlanRef::from(batch_log_seq_scan.clone()), + RequiredDist::single(), + Order::default(), + out_fields, + out_names, + ); + let (batch_log_seq_scan, query_mode) = match session.config().query_mode() { + QueryMode::Auto => (plan_root.gen_batch_local_plan()?, QueryMode::Local), + QueryMode::Local => (plan_root.gen_batch_local_plan()?, QueryMode::Local), + QueryMode::Distributed => ( + plan_root.gen_batch_distributed_plan()?, + QueryMode::Distributed, + ), + }; + Ok(BatchQueryPlanResult { + plan: batch_log_seq_scan, + query_mode, + schema, + stmt_type: StatementType::SELECT, + dependent_relations: table_catalog.dependent_relations.clone(), + }) } } @@ -468,7 +519,8 @@ impl CursorManager { pub async fn add_subscription_cursor( &self, cursor_name: String, - start_timestamp: Option, + start_timestamp: Option, + dependent_table_id: TableId, subscription: Arc, handle_args: &HandlerArgs, ) -> Result<()> { @@ -476,12 +528,21 @@ impl CursorManager { cursor_name.clone(), start_timestamp, subscription, + dependent_table_id, handle_args, ) .await?; - self.cursor_map - .lock() - .await + let mut cursor_map = self.cursor_map.lock().await; + + cursor_map.retain(|_, v| { + if let Cursor::Subscription(cursor) = v { + !matches!(cursor.state, State::Invalid) + } else { + true + } + }); + + cursor_map .try_insert(cursor.cursor_name.clone(), Cursor::Subscription(cursor)) .map_err(|_| { ErrorCode::CatalogError(format!("cursor `{}` already exists", cursor_name).into()) diff --git a/src/frontend/src/stream_fragmenter/mod.rs b/src/frontend/src/stream_fragmenter/mod.rs index e7548ce5fa176..7596a5544b391 100644 --- a/src/frontend/src/stream_fragmenter/mod.rs +++ b/src/frontend/src/stream_fragmenter/mod.rs @@ -285,10 +285,6 @@ fn build_fragment( current_fragment.fragment_type_mask |= FragmentTypeFlag::Sink as u32 } - NodeBody::Subscription(_) => { - current_fragment.fragment_type_mask |= FragmentTypeFlag::Subscription as u32 - } - NodeBody::TopN(_) => current_fragment.requires_singleton = true, NodeBody::StreamScan(node) => { @@ -312,7 +308,7 @@ fn build_fragment( // memorize upstream source id for later use state .dependent_table_ids - .insert(TableId::new(node.upstream_source_id)); + .insert(node.upstream_source_id.into()); current_fragment .upstream_table_ids .push(node.upstream_source_id); diff --git a/src/frontend/src/telemetry.rs b/src/frontend/src/telemetry.rs index 631914cee7a4f..d50b9999b946f 100644 --- a/src/frontend/src/telemetry.rs +++ b/src/frontend/src/telemetry.rs @@ -33,6 +33,7 @@ impl FrontendTelemetryCreator { #[async_trait::async_trait] impl TelemetryReportCreator for FrontendTelemetryCreator { + #[allow(refining_impl_trait)] async fn create_report( &self, tracking_id: String, @@ -97,7 +98,6 @@ mod tests { assert_eq!(report.base.node_type, TelemetryNodeType::Frontend); } - use risingwave_common::telemetry::pb_compatible::TelemetryToProtobuf; use risingwave_common::telemetry::{post_telemetry_report_pb, TELEMETRY_REPORT_URL}; // It is ok to diff --git a/src/frontend/src/test_utils.rs b/src/frontend/src/test_utils.rs index bda6683797251..67bf2794f51b0 100644 --- a/src/frontend/src/test_utils.rs +++ b/src/frontend/src/test_utils.rs @@ -35,14 +35,14 @@ use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta}; use risingwave_pb::backup_service::MetaSnapshotMetadata; use risingwave_pb::catalog::table::OptionalAssociatedSourceId; use risingwave_pb::catalog::{ - PbComment, PbDatabase, PbFunction, PbIndex, PbSchema, PbSink, PbSource, PbSubscription, - PbTable, PbView, Table, + PbComment, PbDatabase, PbFunction, PbIndex, PbSchema, PbSink, PbSource, PbStreamJobStatus, + PbSubscription, PbTable, PbView, Table, }; use risingwave_pb::common::WorkerNode; use risingwave_pb::ddl_service::alter_owner_request::Object; use risingwave_pb::ddl_service::{ alter_set_schema_request, create_connection_request, DdlProgress, PbTableJobType, - ReplaceTablePlan, + ReplaceTablePlan, TableJobType, }; use risingwave_pb::hummock::write_limits::WriteLimit; use risingwave_pb::hummock::{ @@ -55,7 +55,7 @@ use risingwave_pb::meta::list_fragment_distribution_response::FragmentDistributi use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies; use risingwave_pb::meta::list_table_fragment_states_response::TableFragmentState; use risingwave_pb::meta::list_table_fragments_response::TableFragmentInfo; -use risingwave_pb::meta::{EventLog, PbTableParallelism, SystemParams}; +use risingwave_pb::meta::{EventLog, PbTableParallelism, PbThrottleTarget, SystemParams}; use risingwave_pb::stream_plan::StreamFragmentGraph; use risingwave_pb::user::update_user_request::UpdateField; use risingwave_pb::user::{GrantPrivilege, UserInfo}; @@ -64,7 +64,7 @@ use tempfile::{Builder, NamedTempFile}; use crate::catalog::catalog_service::CatalogWriter; use crate::catalog::root_catalog::Catalog; -use crate::catalog::{ConnectionId, DatabaseId, SchemaId}; +use crate::catalog::{ConnectionId, DatabaseId, SchemaId, SecretId}; use crate::error::{ErrorCode, Result}; use crate::handler::RwPgResponse; use crate::meta_client::FrontendMetaClient; @@ -265,6 +265,7 @@ impl CatalogWriter for MockCatalogWriter { _graph: StreamFragmentGraph, ) -> Result<()> { table.id = self.gen_id(); + table.stream_job_status = PbStreamJobStatus::Created as _; self.catalog.write().create_table(&table); self.add_table_or_source_id(table.id, table.schema_id, table.database_id); Ok(()) @@ -296,10 +297,12 @@ impl CatalogWriter for MockCatalogWriter { async fn replace_table( &self, _source: Option, - table: PbTable, + mut table: PbTable, _graph: StreamFragmentGraph, _mapping: ColIndexMapping, + _job_type: TableJobType, ) -> Result<()> { + table.stream_job_status = PbStreamJobStatus::Created as _; self.catalog.write().update_table(&table); Ok(()) } @@ -325,12 +328,8 @@ impl CatalogWriter for MockCatalogWriter { self.create_sink_inner(sink, graph) } - async fn create_subscription( - &self, - subscription: PbSubscription, - graph: StreamFragmentGraph, - ) -> Result<()> { - self.create_subscription_inner(subscription, graph) + async fn create_subscription(&self, subscription: PbSubscription) -> Result<()> { + self.create_subscription_inner(subscription) } async fn create_index( @@ -340,6 +339,7 @@ impl CatalogWriter for MockCatalogWriter { _graph: StreamFragmentGraph, ) -> Result<()> { index_table.id = self.gen_id(); + index_table.stream_job_status = PbStreamJobStatus::Created as _; self.catalog.write().create_table(&index_table); self.add_table_or_index_id( index_table.id, @@ -368,6 +368,17 @@ impl CatalogWriter for MockCatalogWriter { unreachable!() } + async fn create_secret( + &self, + _secret_name: String, + _database_id: u32, + _schema_id: u32, + _owner_id: u32, + _payload: Vec, + ) -> Result<()> { + unreachable!() + } + async fn comment_on(&self, _comment: PbComment) -> Result<()> { unreachable!() } @@ -525,6 +536,10 @@ impl CatalogWriter for MockCatalogWriter { unreachable!() } + async fn drop_secret(&self, _secret_id: SecretId) -> Result<()> { + unreachable!() + } + async fn drop_database(&self, database_id: u32) -> Result<()> { self.catalog.write().drop_database(database_id); Ok(()) @@ -558,7 +573,9 @@ impl CatalogWriter for MockCatalogWriter { for schema in database.iter_schemas() { match object { Object::TableId(table_id) => { - if let Some(table) = schema.get_table_by_id(&TableId::from(table_id)) { + if let Some(table) = + schema.get_created_table_by_id(&TableId::from(table_id)) + { let mut pb_table = table.to_prost(schema.id(), database.id()); pb_table.owner = owner_id; self.catalog.write().update_table(&pb_table); @@ -584,7 +601,7 @@ impl CatalogWriter for MockCatalogWriter { let database_id = self.get_database_id_by_schema(schema_id); let pb_table = { let reader = self.catalog.read(); - let table = reader.get_table_by_id(&table_id.into())?.to_owned(); + let table = reader.get_any_table_by_id(&table_id.into())?.to_owned(); table.to_prost(new_schema_id, database_id) }; self.catalog.write().update_table(&pb_table); @@ -773,11 +790,7 @@ impl MockCatalogWriter { Ok(()) } - fn create_subscription_inner( - &self, - mut subscription: PbSubscription, - _graph: StreamFragmentGraph, - ) -> Result<()> { + fn create_subscription_inner(&self, mut subscription: PbSubscription) -> Result<()> { subscription.id = self.gen_id(); self.catalog.write().create_subscription(&subscription); self.add_table_or_subscription_id( @@ -912,6 +925,8 @@ pub struct MockFrontendMetaClient {} #[async_trait::async_trait] impl FrontendMetaClient for MockFrontendMetaClient { + async fn try_unregister(&self) {} + async fn pin_snapshot(&self) -> RpcResult { Ok(HummockSnapshot { committed_epoch: 0, @@ -1059,6 +1074,24 @@ impl FrontendMetaClient for MockFrontendMetaClient { async fn recover(&self) -> RpcResult<()> { unimplemented!() } + + async fn apply_throttle( + &self, + _kind: PbThrottleTarget, + _id: u32, + _rate_limit: Option, + ) -> RpcResult<()> { + unimplemented!() + } + + async fn list_change_log_epochs( + &self, + _table_id: u32, + _min_epoch: u64, + _max_count: u32, + ) -> RpcResult> { + unimplemented!() + } } #[cfg(test)] diff --git a/src/frontend/src/user/user_catalog.rs b/src/frontend/src/user/user_catalog.rs index a33e9423fc6ea..905348d37dfa6 100644 --- a/src/frontend/src/user/user_catalog.rs +++ b/src/frontend/src/user/user_catalog.rs @@ -16,7 +16,7 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use risingwave_common::acl::{AclMode, AclModeSet}; -use risingwave_pb::user::grant_privilege::{Object as GrantObject, Object}; +use risingwave_pb::user::grant_privilege::{Action, Object as GrantObject, Object}; use risingwave_pb::user::{PbAuthInfo, PbGrantPrivilege, PbUserInfo}; use crate::catalog::{DatabaseId, SchemaId}; @@ -168,4 +168,33 @@ impl UserCatalog { self.get_acl(object) .map_or(false, |acl_set| acl_set.has_mode(mode)) } + + pub fn check_privilege_with_grant_option( + &self, + object: &GrantObject, + actions: &Vec<(Action, bool)>, + ) -> bool { + if self.is_super { + return true; + } + let mut action_map: HashMap<_, _> = actions.iter().map(|action| (action, false)).collect(); + + for privilege in &self.grant_privileges { + if privilege.get_object().unwrap() != object { + continue; + } + for awo in &privilege.action_with_opts { + let action = awo.get_action().unwrap(); + let with_grant_option = awo.with_grant_option; + + for (&key, found) in &mut action_map { + let (required_action, required_grant_option) = *key; + if action == required_action && (!required_grant_option | with_grant_option) { + *found = true; + } + } + } + } + action_map.values().all(|&found| found) + } } diff --git a/src/frontend/src/user/user_privilege.rs b/src/frontend/src/user/user_privilege.rs index 2b474bda0554e..d4ad71e64120d 100644 --- a/src/frontend/src/user/user_privilege.rs +++ b/src/frontend/src/user/user_privilege.rs @@ -94,7 +94,7 @@ pub fn available_prost_privilege(object: PbObject, for_dml_table: bool) -> PbGra &acl::ALL_AVAILABLE_MVIEW_MODES } } - PbObject::ViewId(_) => &acl::ALL_AVAILABLE_VIEW_MODES, + PbObject::ViewId(_) => &acl::ALL_AVAILABLE_TABLE_MODES, PbObject::SinkId(_) => &acl::ALL_AVAILABLE_SINK_MODES, PbObject::SubscriptionId(_) => &acl::ALL_AVAILABLE_SUBSCRIPTION_MODES, PbObject::FunctionId(_) => &acl::ALL_AVAILABLE_FUNCTION_MODES, diff --git a/src/frontend/src/utils/condition.rs b/src/frontend/src/utils/condition.rs index 5161877d1d163..d78adcbd07a97 100644 --- a/src/frontend/src/utils/condition.rs +++ b/src/frontend/src/utils/condition.rs @@ -1018,10 +1018,8 @@ mod cast_compare { #[cfg(test)] mod tests { use rand::Rng; - use risingwave_common::types::DataType; use super::*; - use crate::expr::{FunctionCall, InputRef}; #[test] fn test_split() { diff --git a/src/frontend/src/utils/with_options.rs b/src/frontend/src/utils/with_options.rs index dc3e66bdfbac2..a6984d687bc74 100644 --- a/src/frontend/src/utils/with_options.rs +++ b/src/frontend/src/utils/with_options.rs @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, HashMap}; -use std::convert::TryFrom; +use std::collections::BTreeMap; use std::num::NonZeroU32; -use risingwave_connector::source::kafka::{ +use risingwave_connector::source::kafka::private_link::{ insert_privatelink_broker_rewrite_map, CONNECTION_NAME_KEY, PRIVATELINK_ENDPOINT_KEY, }; use risingwave_connector::WithPropertiesExt; +use risingwave_pb::secret::PbSecretRef; use risingwave_sqlparser::ast::{ CreateConnectionStatement, CreateSinkStatement, CreateSourceStatement, CreateSubscriptionStatement, SqlOption, Statement, Value, @@ -36,7 +36,7 @@ mod options { pub const RETENTION_SECONDS: &str = "retention_seconds"; } -/// Options or properties extracted from the `WITH` clause of DDLs. +/// Options or properties extracted fro m the `WITH` clause of DDLs. #[derive(Default, Clone, Debug, PartialEq, Eq, Hash)] pub struct WithOptions { inner: BTreeMap, @@ -57,17 +57,13 @@ impl std::ops::DerefMut for WithOptions { } impl WithOptions { - /// Create a new [`WithOptions`] from a [`HashMap`]. - pub fn new(inner: HashMap) -> Self { + /// Create a new [`WithOptions`] from a [`BTreeMap`]. + pub fn new(inner: BTreeMap) -> Self { Self { inner: inner.into_iter().collect(), } } - pub fn from_inner(inner: BTreeMap) -> Self { - Self { inner } - } - /// Get the reference of the inner map. pub fn inner(&self) -> &BTreeMap { &self.inner @@ -83,10 +79,13 @@ impl WithOptions { } /// Convert to connector props, remove the key-value pairs used in the top-level. - pub fn into_connector_props(self) -> HashMap { + pub fn into_connector_props(self) -> BTreeMap { self.inner .into_iter() - .filter(|(key, _)| key != OverwriteOptions::STREAMING_RATE_LIMIT_KEY) + .filter(|(key, _)| { + key != OverwriteOptions::STREAMING_RATE_LIMIT_KEY + && key != options::RETENTION_SECONDS + }) .collect() } @@ -121,6 +120,15 @@ impl WithOptions { } } +pub(crate) fn resolve_secret_in_with_options( + _with_options: &mut WithOptions, + _session: &SessionImpl, +) -> RwResult> { + // todo: implement the function and take `resolve_privatelink_in_with_option` as reference + + Ok(BTreeMap::new()) +} + pub(crate) fn resolve_privatelink_in_with_option( with_options: &mut WithOptions, schema_name: &Option, diff --git a/src/java_binding/Cargo.toml b/src/java_binding/Cargo.toml index 477f19878cbd9..0966b700a713f 100644 --- a/src/java_binding/Cargo.toml +++ b/src/java_binding/Cargo.toml @@ -10,13 +10,32 @@ ignored = ["workspace-hack"] normal = ["workspace-hack"] [dependencies] +anyhow = "1" +bytes = "1" +cfg-or-panic = "0.2" +foyer ={ workspace = true } +futures = { version = "0.3", default-features = false, features = ["alloc"] } jni = "0.21.1" prost = { workspace = true } risingwave_common = { workspace = true } +risingwave_hummock_sdk = { workspace = true } risingwave_jni_core = { workspace = true } +risingwave_object_store = { workspace = true } risingwave_pb = { workspace = true } +risingwave_storage = { workspace = true } +rw_futures_util = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +tokio = { version = "0.2", package = "madsim-tokio", features = [ + "fs", + "rt", + "rt-multi-thread", + "sync", + "macros", + "time", + "signal", +] } +tracing = "0.1" [dev-dependencies] risingwave_expr = { workspace = true } diff --git a/src/jni_core/src/hummock_iterator.rs b/src/java_binding/src/hummock_iterator.rs similarity index 76% rename from src/jni_core/src/hummock_iterator.rs rename to src/java_binding/src/hummock_iterator.rs index 42e584fb3820c..5cfeb0ecf7e04 100644 --- a/src/jni_core/src/hummock_iterator.rs +++ b/src/java_binding/src/hummock_iterator.rs @@ -14,86 +14,101 @@ use std::sync::Arc; +use anyhow::anyhow; use bytes::Bytes; -use futures::{Stream, TryStreamExt}; +use foyer::HybridCacheBuilder; +use futures::{TryFutureExt, TryStreamExt}; use risingwave_common::catalog::ColumnDesc; -use risingwave_common::config::{EvictionConfig, MetricLevel, ObjectStoreConfig}; +use risingwave_common::config::{MetricLevel, ObjectStoreConfig}; use risingwave_common::hash::VirtualNode; use risingwave_common::row::OwnedRow; use risingwave_common::util::value_encoding::column_aware_row_encoding::ColumnAwareSerde; use risingwave_common::util::value_encoding::{BasicSerde, EitherSerde, ValueRowDeserializer}; use risingwave_hummock_sdk::key::{prefixed_range_with_vnode, TableKeyRange}; use risingwave_hummock_sdk::version::HummockVersion; +use risingwave_jni_core::HummockJavaBindingIterator; use risingwave_object_store::object::build_remote_object_store; use risingwave_object_store::object::object_metrics::ObjectStoreMetrics; use risingwave_pb::java_binding::key_range::Bound; use risingwave_pb::java_binding::{KeyRange, ReadPlan}; -use risingwave_storage::error::StorageResult; +use risingwave_storage::error::{StorageError, StorageResult}; use risingwave_storage::hummock::local_version::pinned_version::PinnedVersion; use risingwave_storage::hummock::store::version::HummockVersionReader; use risingwave_storage::hummock::store::HummockStorageIterator; use risingwave_storage::hummock::{ - get_committed_read_version_tuple, CachePolicy, FileCache, SstableStore, SstableStoreConfig, + get_committed_read_version_tuple, CachePolicy, HummockError, SstableStore, SstableStoreConfig, }; use risingwave_storage::monitor::{global_hummock_state_store_metrics, HummockStateStoreMetrics}; use risingwave_storage::row_serde::value_serde::ValueRowSerdeNew; use risingwave_storage::store::{ReadOptions, StateStoreIterExt}; -use risingwave_storage::table::KeyedRow; use rw_futures_util::select_all; use tokio::sync::mpsc::unbounded_channel; -type SelectAllIterStream = impl Stream>> + Unpin; -type SingleIterStream = impl Stream>>; +type SingleIterStream = HummockJavaBindingIterator; -fn select_all_vnode_stream(streams: Vec) -> SelectAllIterStream { - select_all(streams.into_iter().map(Box::pin)) +fn select_all_vnode_stream(streams: Vec) -> HummockJavaBindingIterator { + Box::pin(select_all(streams)) } fn to_deserialized_stream( iter: HummockStorageIterator, row_serde: EitherSerde, ) -> SingleIterStream { - iter.into_stream(move |(key, value)| { - Ok(KeyedRow::new( - key.user_key.table_key.copy_into(), - row_serde.deserialize(value).map(OwnedRow::new)?, - )) - }) + Box::pin( + iter.into_stream(move |(key, value)| { + Ok(( + Bytes::copy_from_slice(key.user_key.table_key.0), + row_serde.deserialize(value).map(OwnedRow::new)?, + )) + }) + .map_err(|e| anyhow!(e)), + ) } -pub struct HummockJavaBindingIterator { - stream: SelectAllIterStream, -} - -impl HummockJavaBindingIterator { - pub async fn new(read_plan: ReadPlan) -> StorageResult { +pub(crate) async fn new_hummock_java_binding_iter( + read_plan: ReadPlan, +) -> StorageResult { + { // Note(bugen): should we forward the implementation to the `StorageTable`? let object_store = Arc::new( build_remote_object_store( &read_plan.object_store_url, Arc::new(ObjectStoreMetrics::unused()), "Hummock", - ObjectStoreConfig::default(), + Arc::new(ObjectStoreConfig::default()), ) .await, ); + + let meta_cache = HybridCacheBuilder::new() + .memory(1 << 10) + .with_shards(2) + .storage() + .build() + .map_err(HummockError::foyer_error) + .map_err(StorageError::from) + .await?; + let block_cache = HybridCacheBuilder::new() + .memory(1 << 10) + .with_shards(2) + .storage() + .build() + .map_err(HummockError::foyer_error) + .map_err(StorageError::from) + .await?; + let sstable_store = Arc::new(SstableStore::new(SstableStoreConfig { store: object_store, path: read_plan.data_dir, - block_cache_capacity: 1 << 10, - block_cache_shard_num: 2, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_capacity: 1 << 10, - meta_cache_shard_num: 2, - meta_cache_eviction: EvictionConfig::for_test(), prefetch_buffer_capacity: 1 << 10, max_prefetch_block_number: 16, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), recent_filter: None, state_store_metrics: Arc::new(global_hummock_state_store_metrics( MetricLevel::Disabled, )), + use_new_object_prefix_strategy: read_plan.use_new_object_prefix_strategy, + meta_cache, + block_cache, })); let reader = HummockVersionReader::new( sstable_store, @@ -157,11 +172,7 @@ impl HummockJavaBindingIterator { let stream = select_all_vnode_stream(streams); - Ok(Self { stream }) - } - - pub async fn next(&mut self) -> StorageResult>> { - self.stream.try_next().await + Ok(stream) } } diff --git a/src/java_binding/src/lib.rs b/src/java_binding/src/lib.rs index ef5bb228b0cab..4fd089918bd5b 100644 --- a/src/java_binding/src/lib.rs +++ b/src/java_binding/src/lib.rs @@ -12,16 +12,83 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![feature(type_alias_impl_trait)] +#![feature(try_blocks)] + +mod hummock_iterator; use std::ffi::c_void; +use std::ops::Deref; +use anyhow::anyhow; +use cfg_or_panic::cfg_or_panic; +use jni::objects::JByteArray; use jni::sys::{jint, JNI_VERSION_1_2}; -use jni::JavaVM; -use risingwave_jni_core::register_native_method_for_jvm; +use jni::{JNIEnv, JavaVM}; +use prost::Message; +use risingwave_common::error::AsReport; +use risingwave_jni_core::jvm_runtime::{jvm_env, register_java_binding_native_methods}; +use risingwave_jni_core::{ + execute_and_catch, gen_class_name, to_guarded_slice, EnvParam, JavaBindingIterator, Pointer, + JAVA_BINDING_ASYNC_RUNTIME, +}; + +use crate::hummock_iterator::new_hummock_java_binding_iter; + +fn register_hummock_java_binding_native_methods( + env: &mut JNIEnv<'_>, +) -> Result<(), jni::errors::Error> { + let binding_class = env + .find_class(gen_class_name!(com.risingwave.java.binding.HummockIterator)) + .inspect_err(|e| tracing::error!(error = ?e.as_report(), "jvm find class error"))?; + macro_rules! gen_native_method_array { + () => {{ + risingwave_jni_core::split_extract_plain_native_methods! {{long iteratorNewHummock(byte[] readPlan);}, gen_native_method_array} + }}; + ({$({ $func_name:ident, {$($ret:tt)+}, {$($args:tt)*} })*}) => { + [ + $( + risingwave_jni_core::gen_native_method_entry! { + Java_com_risingwave_java_binding_HummockIterator_, $func_name, {$($ret)+}, {$($args)*} + }, + )* + ] + } + } + env.register_native_methods(binding_class, &gen_native_method_array!()) + .inspect_err( + |e| tracing::error!(error = ?e.as_report(), "jvm register native methods error"), + )?; + + tracing::info!("register native methods for jvm successfully"); + Ok(()) +} #[no_mangle] #[allow(non_snake_case)] pub extern "system" fn JNI_OnLoad(jvm: JavaVM, _reserved: *mut c_void) -> jint { - let _ = register_native_method_for_jvm(&jvm) - .inspect_err(|_e| eprintln!("unable to register native method")); + let result: Result<(), jni::errors::Error> = try { + let mut env = jvm_env(&jvm)?; + register_java_binding_native_methods(&mut env)?; + register_hummock_java_binding_native_methods(&mut env)?; + }; + let _ = + result.inspect_err(|e| eprintln!("unable to register native method: {:?}", e.as_report())); + JNI_VERSION_1_2 } + +#[cfg_or_panic(not(madsim))] +#[no_mangle] +extern "system" fn Java_com_risingwave_java_binding_HummockIterator_iteratorNewHummock<'a>( + env: EnvParam<'a>, + read_plan: JByteArray<'a>, +) -> Pointer<'static, JavaBindingIterator<'static>> { + execute_and_catch(env, move |env| { + let read_plan = Message::decode(to_guarded_slice(&read_plan, env)?.deref())?; + let iter = JAVA_BINDING_ASYNC_RUNTIME + .block_on(new_hummock_java_binding_iter(read_plan)) + .map_err(|e| anyhow!(e))?; + let iter = JavaBindingIterator::new_hummock_iter(iter); + Ok(iter.into()) + }) +} diff --git a/src/jni_core/Cargo.toml b/src/jni_core/Cargo.toml index 1e2e2fb3c8218..a16776add6c6f 100644 --- a/src/jni_core/Cargo.toml +++ b/src/jni_core/Cargo.toml @@ -14,6 +14,7 @@ anyhow = "1" bytes = "1" cfg-or-panic = "0.2" chrono = { version = "0.4", default-features = false } +foyer ={ workspace = true } fs-err = "2" futures = { version = "0.3", default-features = false, features = ["alloc"] } itertools = { workspace = true } @@ -21,10 +22,7 @@ jni = { version = "0.21.1", features = ["invocation"] } paste = "1" prost = { workspace = true } risingwave_common = { workspace = true } -risingwave_hummock_sdk = { workspace = true } -risingwave_object_store = { workspace = true } risingwave_pb = { workspace = true } -risingwave_storage = { workspace = true } rw_futures_util = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/src/jni_core/src/jvm_runtime.rs b/src/jni_core/src/jvm_runtime.rs index c369ad7b38939..15bff84fd5749 100644 --- a/src/jni_core/src/jvm_runtime.rs +++ b/src/jni_core/src/jvm_runtime.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::option::Option::Some; use std::ffi::c_void; use std::path::PathBuf; use std::sync::OnceLock; @@ -21,13 +20,12 @@ use anyhow::{bail, Context}; use fs_err as fs; use fs_err::PathExt; use jni::objects::{JObject, JString}; -use jni::strings::JNIString; -use jni::{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM, NativeMethod}; +use jni::{AttachGuard, InitArgsBuilder, JNIEnv, JNIVersion, JavaVM}; use risingwave_common::util::resource_util::memory::system_memory_available_bytes; use thiserror_ext::AsReport; use tracing::error; -use crate::call_method; +use crate::{call_method, call_static_method}; /// Use 10% of compute total memory by default. Compute node uses 0.7 * system memory by default. const DEFAULT_MEMORY_PROPORTION: f64 = 0.07; @@ -107,8 +105,7 @@ impl JavaVmWrapper { .option("-Dis_embedded_connector=true") .option(format!("-Djava.class.path={}", class_vec.join(":"))) .option("-Xms16m") - .option(format!("-Xmx{}", jvm_heap_size)) - .option("-Dcdc.source.wait.streaming.before.exit.seconds=30"); + .option(format!("-Xmx{}", jvm_heap_size)); tracing::info!("JVM args: {:?}", args_builder); let jvm_args = args_builder.build().context("invalid jvm args")?; @@ -124,19 +121,27 @@ impl JavaVmWrapper { tracing::info!("initialize JVM successfully"); - register_native_method_for_jvm(&jvm).context("failed to register native method")?; + let result: std::result::Result<(), jni::errors::Error> = try { + let mut env = jvm_env(&jvm)?; + register_java_binding_native_methods(&mut env)?; + }; + + result.context("failed to register native method")?; Ok(jvm) } } -pub fn register_native_method_for_jvm(jvm: &JavaVM) -> Result<(), jni::errors::Error> { - let mut env = jvm - .attach_current_thread() - .inspect_err(|e| tracing::error!(error = ?e.as_report(), "jvm attach thread error"))?; +pub fn jvm_env(jvm: &JavaVM) -> Result, jni::errors::Error> { + jvm.attach_current_thread() + .inspect_err(|e| tracing::error!(error = ?e.as_report(), "jvm attach thread error")) +} +pub fn register_java_binding_native_methods( + env: &mut JNIEnv<'_>, +) -> Result<(), jni::errors::Error> { let binding_class = env - .find_class("com/risingwave/java/binding/Binding") + .find_class(gen_class_name!(com.risingwave.java.binding.Binding)) .inspect_err(|e| tracing::error!(error = ?e.as_report(), "jvm find class error"))?; use crate::*; macro_rules! gen_native_method_array { @@ -146,14 +151,8 @@ pub fn register_native_method_for_jvm(jvm: &JavaVM) -> Result<(), jni::errors::E ({$({ $func_name:ident, {$($ret:tt)+}, {$($args:tt)*} })*}) => { [ $( - { - let fn_ptr = paste::paste! {[ ]} as *mut c_void; - let sig = $crate::gen_jni_sig! { {$($ret)+}, {$($args)*}}; - NativeMethod { - name: JNIString::from(stringify! {$func_name}), - sig: JNIString::from(sig), - fn_ptr, - } + $crate::gen_native_method_entry! { + Java_com_risingwave_java_binding_Binding_, $func_name, {$($ret)+}, {$($args)*} }, )* ] @@ -173,21 +172,21 @@ pub fn register_native_method_for_jvm(jvm: &JavaVM) -> Result<(), jni::errors::E pub fn load_jvm_memory_stats() -> (usize, usize) { match JVM.get() { Some(jvm) => { - let result: Result<(usize, usize), jni::errors::Error> = try { - let mut env = jvm.attach_current_thread()?; - - let runtime_instance = crate::call_static_method!( - env, - {Runtime}, - {Runtime getRuntime()} - )?; - - let total_memory = - call_method!(env, runtime_instance.as_ref(), {long totalMemory()})?; - let free_memory = - call_method!(env, runtime_instance.as_ref(), {long freeMemory()})?; - - (total_memory as usize, (total_memory - free_memory) as usize) + let result: Result<(usize, usize), anyhow::Error> = try { + execute_with_jni_env(jvm, |env| { + let runtime_instance = crate::call_static_method!( + env, + {Runtime}, + {Runtime getRuntime()} + )?; + + let total_memory = + call_method!(env, runtime_instance.as_ref(), {long totalMemory()})?; + let free_memory = + call_method!(env, runtime_instance.as_ref(), {long freeMemory()})?; + + Ok((total_memory as usize, (total_memory - free_memory) as usize)) + })? }; match result { Ok(ret) => ret, @@ -205,11 +204,32 @@ pub fn execute_with_jni_env( jvm: &JavaVM, f: impl FnOnce(&mut JNIEnv<'_>) -> anyhow::Result, ) -> anyhow::Result { - let _guard = jvm + let mut env = jvm .attach_current_thread() .with_context(|| "Failed to attach current rust thread to jvm")?; - let mut env = jvm.get_env().with_context(|| "Failed to get jni env")?; + // set context class loader for the thread + // java.lang.Thread.currentThread() + // .setContextClassLoader(java.lang.ClassLoader.getSystemClassLoader()); + + let thread = crate::call_static_method!( + env, + {Thread}, + {Thread currentThread()} + )?; + + let system_class_loader = crate::call_static_method!( + env, + {ClassLoader}, + {ClassLoader getSystemClassLoader()} + )?; + + crate::call_method!( + env, + thread, + {void setContextClassLoader(ClassLoader)}, + &system_class_loader + )?; let ret = f(&mut env); @@ -242,3 +262,32 @@ pub fn jobj_to_str(env: &mut JNIEnv<'_>, obj: JObject<'_>) -> anyhow::Result anyhow::Result> { + match JVM.get() { + None => Ok(None), + Some(jvm) => execute_with_jni_env(jvm, |env| { + let result = call_static_method!( + env, + {com.risingwave.connector.api.Monitor}, + {String dumpStackTrace()} + ) + .with_context(|| "Failed to call Java function")?; + let result = JString::from(result); + let result = env + .get_string(&result) + .with_context(|| "Failed to convert JString")?; + let result = result + .to_str() + .with_context(|| "Failed to convert JavaStr")?; + Ok(Some(result.to_string())) + }), + } +} diff --git a/src/jni_core/src/lib.rs b/src/jni_core/src/lib.rs index 7ff8e5aa930e8..18d1807948d21 100644 --- a/src/jni_core/src/lib.rs +++ b/src/jni_core/src/lib.rs @@ -18,7 +18,6 @@ #![feature(type_alias_impl_trait)] #![feature(try_blocks)] -pub mod hummock_iterator; pub mod jvm_runtime; mod macros; mod tracing_slf4j; @@ -33,6 +32,8 @@ use anyhow::anyhow; use bytes::Bytes; use cfg_or_panic::cfg_or_panic; use chrono::{Datelike, NaiveDateTime, Timelike}; +use futures::stream::BoxStream; +use futures::TryStreamExt; use jni::objects::{ AutoElements, GlobalRef, JByteArray, JClass, JMethodID, JObject, JStaticMethodID, JString, JValueOwned, ReleaseMode, @@ -42,6 +43,7 @@ use jni::sys::{ jboolean, jbyte, jdouble, jfloat, jint, jlong, jshort, jsize, jvalue, JNI_FALSE, JNI_TRUE, }; use jni::JNIEnv; +pub use paste::paste; use prost::{DecodeError, Message}; use risingwave_common::array::{ArrayError, StreamChunk}; use risingwave_common::hash::VirtualNode; @@ -54,17 +56,14 @@ use risingwave_pb::connector_service::{ SinkWriterStreamRequest, SinkWriterStreamResponse, }; use risingwave_pb::data::Op; -use risingwave_storage::error::StorageError; use thiserror::Error; use thiserror_ext::AsReport; use tokio::runtime::Runtime; use tokio::sync::mpsc::{Receiver, Sender}; use tracing_slf4j::*; -use crate::hummock_iterator::HummockJavaBindingIterator; -pub use crate::jvm_runtime::register_native_method_for_jvm; - -static RUNTIME: LazyLock = LazyLock::new(|| tokio::runtime::Runtime::new().unwrap()); +pub static JAVA_BINDING_ASYNC_RUNTIME: LazyLock = + LazyLock::new(|| tokio::runtime::Runtime::new().unwrap()); #[derive(Error, Debug)] pub enum BindingError { @@ -78,7 +77,7 @@ pub enum BindingError { #[error("StorageError {error}")] Storage { #[from] - error: StorageError, + error: anyhow::Error, backtrace: Backtrace, }, @@ -201,7 +200,7 @@ impl<'a> EnvParam<'a> { } } -fn execute_and_catch<'env, F, Ret>(mut env: EnvParam<'env>, inner: F) -> Ret +pub fn execute_and_catch<'env, F, Ret>(mut env: EnvParam<'env>, inner: F) -> Ret where F: FnOnce(&mut EnvParam<'env>) -> Result, Ret: Default + 'env, @@ -245,9 +244,10 @@ struct JavaClassMethodCache { } // TODO: may only return a RowRef -type StreamChunkRowIterator<'a> = impl Iterator + 'a; +pub type StreamChunkRowIterator<'a> = impl Iterator + 'a; +pub type HummockJavaBindingIterator = BoxStream<'static, anyhow::Result<(Bytes, OwnedRow)>>; -enum JavaBindingIteratorInner<'a> { +pub enum JavaBindingIteratorInner<'a> { Hummock(HummockJavaBindingIterator), StreamChunk(StreamChunkRowIterator<'a>), } @@ -288,12 +288,22 @@ struct RowCursor { extra: RowExtra, } -struct JavaBindingIterator<'a> { +pub struct JavaBindingIterator<'a> { inner: JavaBindingIteratorInner<'a>, cursor: Option, class_cache: JavaClassMethodCache, } +impl JavaBindingIterator<'static> { + pub fn new_hummock_iter(iter: HummockJavaBindingIterator) -> Self { + Self { + inner: JavaBindingIteratorInner::Hummock(iter), + cursor: None, + class_cache: Default::default(), + } + } +} + impl<'a> Deref for JavaBindingIterator<'a> { type Target = OwnedRow; @@ -311,24 +321,6 @@ extern "system" fn Java_com_risingwave_java_binding_Binding_vnodeCount(_env: Env VirtualNode::COUNT as jint } -#[cfg_or_panic(not(madsim))] -#[no_mangle] -extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorNewHummock<'a>( - env: EnvParam<'a>, - read_plan: JByteArray<'a>, -) -> Pointer<'static, JavaBindingIterator<'static>> { - execute_and_catch(env, move |env| { - let read_plan = Message::decode(to_guarded_slice(&read_plan, env)?.deref())?; - let iter = RUNTIME.block_on(HummockJavaBindingIterator::new(read_plan))?; - let iter = JavaBindingIterator { - inner: JavaBindingIteratorInner::Hummock(iter), - cursor: None, - class_cache: Default::default(), - }; - Ok(iter.into()) - }) -} - #[cfg_or_panic(not(madsim))] #[no_mangle] extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorNewStreamChunk<'a>( @@ -355,16 +347,15 @@ extern "system" fn Java_com_risingwave_java_binding_Binding_iteratorNext<'a>( let iter = pointer.as_mut(); match &mut iter.inner { JavaBindingIteratorInner::Hummock(ref mut hummock_iter) => { - match RUNTIME.block_on(hummock_iter.next())? { + match JAVA_BINDING_ASYNC_RUNTIME.block_on(hummock_iter.try_next())? { None => { iter.cursor = None; Ok(JNI_FALSE) } - Some(keyed_row) => { - let (key, row) = keyed_row.into_parts(); + Some((key, row)) => { iter.cursor = Some(RowCursor { row, - extra: RowExtra::Key(key.0), + extra: RowExtra::Key(key), }); Ok(JNI_TRUE) } diff --git a/src/jni_core/src/macros.rs b/src/jni_core/src/macros.rs index 1b2f79f829564..982ccda06ecf0 100644 --- a/src/jni_core/src/macros.rs +++ b/src/jni_core/src/macros.rs @@ -448,10 +448,6 @@ macro_rules! for_all_plain_native_methods { public static native int vnodeCount(); - // hummock iterator method - // Return a pointer to the iterator - static native long iteratorNewHummock(byte[] readPlan); - static native long iteratorNewStreamChunk(long pointer); static native boolean iteratorNext(long pointer); @@ -839,6 +835,23 @@ macro_rules! call_method { }}; } +#[macro_export] +macro_rules! gen_native_method_entry { + ( + $class_prefix:ident, $func_name:ident, {$($ret:tt)+}, {$($args:tt)*} + ) => {{ + { + let fn_ptr = $crate::paste! {[<$class_prefix $func_name> ]} as *mut c_void; + let sig = $crate::gen_jni_sig! { {$($ret)+}, {$($args)*}}; + jni::NativeMethod { + name: jni::strings::JNIString::from(stringify! {$func_name}), + sig: jni::strings::JNIString::from(sig), + fn_ptr, + } + } + }}; +} + #[cfg(test)] mod tests { use std::fmt::Formatter; @@ -891,7 +904,6 @@ mod tests { tracingSlf4jEvent (Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V, tracingSlf4jEventEnabled (I)Z, vnodeCount ()I, - iteratorNewHummock ([B)J, iteratorNewStreamChunk (J)J, iteratorNext (J)Z, iteratorClose (J)V, diff --git a/src/license/Cargo.toml b/src/license/Cargo.toml new file mode 100644 index 0000000000000..47e00228626b8 --- /dev/null +++ b/src/license/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "risingwave_license" +description = "License validation and feature gating for RisingWave" +version = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +keywords = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[package.metadata.cargo-machete] +ignored = ["workspace-hack"] + +[package.metadata.cargo-udeps.ignore] +normal = ["workspace-hack"] + +[dependencies] +jsonwebtoken = "9" +serde = { version = "1", features = ["derive"] } +thiserror = "1" +thiserror-ext = { workspace = true } +tracing = "0.1" + +[dev-dependencies] +expect-test = "1" + +[lints] +workspace = true diff --git a/src/license/src/feature.rs b/src/license/src/feature.rs new file mode 100644 index 0000000000000..302538cc3ecc3 --- /dev/null +++ b/src/license/src/feature.rs @@ -0,0 +1,127 @@ +// Copyright 2024 RisingWave Labs +// +// 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 thiserror::Error; + +use super::{License, LicenseKeyError, LicenseManager, Tier}; + +/// Define all features that are available based on the tier of the license. +/// +/// # Define a new feature +/// +/// To add a new feature, add a new entry below following the same pattern as the existing ones. +/// +/// Check the definition of [`Tier`] for all available tiers. Note that normally there's no need to +/// add a feature with the minimum tier of `Free`, as you can directly write the code without +/// gating it with a feature check. +/// +/// # Check the availability of a feature +/// +/// To check the availability of a feature during runtime, call the method +/// [`check_available`](Feature::check_available) on the feature. If the feature is not available, +/// an error of type [`FeatureNotAvailable`] will be returned and you should handle it properly, +/// generally by returning an error to the user. +/// +/// # Feature availability in tests +/// +/// In tests with `debug_assertions` enabled, a license key of the paid (maximum) tier is set by +/// default. As a result, all features are available in tests. To test the behavior when a feature +/// is not available, you can manually set a license key with a lower tier. Check the e2e test cases +/// under `error_ui` for examples. +macro_rules! for_all_features { + ($macro:ident) => { + $macro! { + // name min tier doc + { TestPaid, Paid, "A dummy feature that's only available on paid tier for testing purposes." }, + } + }; +} + +macro_rules! def_feature { + ($({ $name:ident, $min_tier:ident, $doc:literal },)*) => { + /// A set of features that are available based on the tier of the license. + /// + /// To define a new feature, add a new entry in the macro [`for_all_features`]. + #[derive(Clone, Copy, Debug)] + pub enum Feature { + $( + #[doc = concat!($doc, "\n\nAvailable for tier `", stringify!($min_tier), "` and above.")] + $name, + )* + } + + impl Feature { + /// Minimum tier required to use this feature. + fn min_tier(self) -> Tier { + match self { + $( + Self::$name => Tier::$min_tier, + )* + } + } + } + }; +} + +for_all_features!(def_feature); + +/// The error type for feature not available due to license. +#[derive(Debug, Error)] +pub enum FeatureNotAvailable { + #[error( + "feature {:?} is only available for tier {:?} and above, while the current tier is {:?}\n\n\ + Hint: You may want to set a license key with `ALTER SYSTEM SET license_key = '...';` command.", + feature, feature.min_tier(), current_tier, + )] + InsufficientTier { + feature: Feature, + current_tier: Tier, + }, + + #[error("feature {feature:?} is not available due to license error")] + LicenseError { + feature: Feature, + source: LicenseKeyError, + }, +} + +impl Feature { + /// Check whether the feature is available based on the current license. + pub fn check_available(self) -> Result<(), FeatureNotAvailable> { + match LicenseManager::get().license() { + Ok(license) => { + if license.tier >= self.min_tier() { + Ok(()) + } else { + Err(FeatureNotAvailable::InsufficientTier { + feature: self, + current_tier: license.tier, + }) + } + } + Err(error) => { + // If there's a license key error, we still try against the default license first + // to see if the feature is available for free. + if License::default().tier >= self.min_tier() { + Ok(()) + } else { + Err(FeatureNotAvailable::LicenseError { + feature: self, + source: error, + }) + } + } + } + } +} diff --git a/src/license/src/key.pub b/src/license/src/key.pub new file mode 100644 index 0000000000000..8c57095eb3e39 --- /dev/null +++ b/src/license/src/key.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv9mzpTfLkQc8S2R6rgRG +nX/uBtXYcieUWV1ppLOW0iOfOu3Ao8FzYY2RIaLrJ0Wi3I7qktBV+lMnn2ncqD73 +1Rfo/RD4/M8WGm93+MLVTNEw5Wn1FNf9M8lC1Zo3iPlssVbVYlIJZDkZZ5phF3qo +PHvs3zm0XtglF3JfAzo0ecmfh+q3Jjjc2j5Ceu+Ngcm3wJV4aVSvK6C802e6ONbq +OwwMCie960hdpVHArBpeI6FbPFMSR4f9tCB6gSIP4sQmXFUPmvw2Khc9cjFc/QpP +WzmCpyvUeK0XhV4LYPA2+vQ4Ui5I8de/igR2JUANUNOJ7vP3kLc57g8DgxTtE75/ +LQIDAQAB +-----END PUBLIC KEY----- diff --git a/src/sqlparser/test_runner/tests/test_runner.rs b/src/license/src/lib.rs similarity index 79% rename from src/sqlparser/test_runner/tests/test_runner.rs rename to src/license/src/lib.rs index 501e27f33ab39..0e641be9789b1 100644 --- a/src/sqlparser/test_runner/tests/test_runner.rs +++ b/src/license/src/lib.rs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Traverses the 'testdata/' directory and runs all files. -#[test] -fn run_all_test_files() { - risingwave_sqlparser_test_runner::run_all_test_files(); -} +#![feature(lazy_cell)] + +mod feature; +mod manager; + +pub use feature::*; +pub use manager::*; diff --git a/src/license/src/manager.rs b/src/license/src/manager.rs new file mode 100644 index 0000000000000..f815a145ba01d --- /dev/null +++ b/src/license/src/manager.rs @@ -0,0 +1,293 @@ +// Copyright 2024 RisingWave Labs +// +// 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::{LazyLock, RwLock}; + +use jsonwebtoken::{Algorithm, DecodingKey, Validation}; +use serde::Deserialize; +use thiserror::Error; +use thiserror_ext::AsReport; + +/// License tier. +/// +/// Each enterprise [`Feature`](super::Feature) is available for a specific tier and above. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Tier { + /// Free tier. + /// + /// This is more like a placeholder. If a feature is available for the free tier, there's no + /// need to add it to the [`Feature`](super::Feature) enum at all. + Free, + + /// Paid tier. + // TODO(license): Add more tiers if needed. + Paid, +} + +/// Issuer of the license. +/// +/// The issuer must be `prod.risingwave.com` in production, and can be `test.risingwave.com` in +/// development. This will be validated when refreshing the license key. +#[derive(Debug, Clone, Deserialize)] +pub enum Issuer { + #[serde(rename = "prod.risingwave.com")] + Prod, + + #[serde(rename = "test.risingwave.com")] + Test, + + #[serde(untagged)] + Unknown(String), +} + +/// The content of a license. +/// +/// We use JSON Web Token (JWT) to represent the license. This struct is the payload. +// TODO(license): Shall we add a version field? +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "snake_case")] +pub(super) struct License { + /// Subject of the license. + /// + /// See . + #[allow(dead_code)] + pub sub: String, + + /// Issuer of the license. + /// + /// See . + #[allow(dead_code)] + pub iss: Issuer, + + /// Tier of the license. + pub tier: Tier, + + /// Expiration time in seconds since UNIX epoch. + /// + /// See . + pub exp: u64, +} + +impl Default for License { + /// The default license is a free license that never expires. + /// + /// Used when `license_key` is unset or invalid. + fn default() -> Self { + Self { + sub: "default".to_owned(), + tier: Tier::Free, + iss: Issuer::Prod, + exp: u64::MAX, + } + } +} + +/// The error type for invalid license key when verifying as JWT. +#[derive(Debug, Clone, Error)] +#[error("invalid license key")] +pub struct LicenseKeyError(#[source] jsonwebtoken::errors::Error); + +struct Inner { + license: Result, +} + +/// The singleton license manager. +pub struct LicenseManager { + inner: RwLock, +} + +static PUBLIC_KEY: LazyLock = LazyLock::new(|| { + DecodingKey::from_rsa_pem(include_bytes!("key.pub")) + .expect("invalid public key for license validation") +}); + +impl LicenseManager { + /// Create a new license manager with the default license. + fn new() -> Self { + Self { + inner: RwLock::new(Inner { + license: Ok(License::default()), + }), + } + } + + /// Get the singleton instance of the license manager. + pub fn get() -> &'static Self { + static INSTANCE: LazyLock = LazyLock::new(LicenseManager::new); + &INSTANCE + } + + /// Refresh the license with the given license key. + pub fn refresh(&self, license_key: &str) { + let mut inner = self.inner.write().unwrap(); + + // Empty license key means unset. Use the default one here. + if license_key.is_empty() { + inner.license = Ok(License::default()); + return; + } + + // TODO(license): shall we also validate `nbf`(Not Before)? + let mut validation = Validation::new(Algorithm::RS512); + // Only accept `prod` issuer in production, so that we can use license keys issued by + // the `test` issuer in development without leaking them to production. + validation.set_issuer(&[ + "prod.risingwave.com", + #[cfg(debug_assertions)] + "test.risingwave.com", + ]); + + inner.license = match jsonwebtoken::decode(license_key, &PUBLIC_KEY, &validation) { + Ok(data) => Ok(data.claims), + Err(error) => Err(LicenseKeyError(error)), + }; + + match &inner.license { + Ok(license) => tracing::info!(?license, "license refreshed"), + Err(error) => tracing::warn!(error = %error.as_report(), "invalid license key"), + } + } + + /// Get the current license if it is valid. + /// + /// Since the license can expire, the returned license should not be cached by the caller. + pub(super) fn license(&self) -> Result { + let license = self.inner.read().unwrap().license.clone()?; + + // Check the expiration time additionally. + if license.exp < jsonwebtoken::get_current_timestamp() { + return Err(LicenseKeyError( + jsonwebtoken::errors::ErrorKind::ExpiredSignature.into(), + )); + } + + Ok(license) + } +} + +/// A license key with the paid tier that only works in tests. +pub const TEST_PAID_LICENSE_KEY: &str = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.\ + eyJzdWIiOiJydy10ZXN0IiwidGllciI6InBhaWQiLCJpc3MiOiJ0ZXN0LnJpc2luZ3dhdmUuY29tIiwiZXhwIjo5OTk5OTk5OTk5fQ.\ + c6Gmb6xh3dBDYX_4cOnHUbwRXJbUCM7W3mrJA77nLC5FkoOLpGstzvQ7qfnPVBu412MFtKRDvh-Lk8JwG7pVa0WLw16DeHTtVHxZukMTZ1Q_ciZ1xKeUx_pwUldkVzv6c9j99gNqPSyTjzOXTdKlidBRLer2zP0v3Lf-ZxnMG0tEcIbTinTb3BNCtAQ8bwBSRP-X48cVTWafjaZxv_zGiJT28uV3bR6jwrorjVB4VGvqhsJi6Fd074XOmUlnOleoAtyzKvjmGC5_FvnL0ztIe_I0z_pyCMfWpyJ_J4C7rCP1aVWUImyoowLmVDA-IKjclzOW5Fvi0wjXsc6OckOc_A"; + +// Tests below only work in debug mode. +#[cfg(debug_assertions)] +#[cfg(test)] +mod tests { + use expect_test::expect; + + use super::*; + + fn do_test(key: &str, expect: expect_test::Expect) { + let manager = LicenseManager::new(); + manager.refresh(key); + + match manager.license() { + Ok(license) => expect.assert_debug_eq(&license), + Err(error) => expect.assert_eq(&error.to_report_string()), + } + } + + #[test] + fn test_paid_license_key() { + do_test( + TEST_PAID_LICENSE_KEY, + expect![[r#" + License { + sub: "rw-test", + iss: Test, + tier: Paid, + exp: 9999999999, + } + "#]], + ); + } + + #[test] + fn test_free_license_key() { + const KEY: &str = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.\ + eyJzdWIiOiJydy10ZXN0IiwidGllciI6ImZyZWUiLCJpc3MiOiJ0ZXN0LnJpc2luZ3dhdmUuY29tIiwiZXhwIjo5OTk5OTk5OTk5fQ.\ + ALC3Kc9LI6u0S-jeMB1YTxg1k8Azxwvc750ihuSZgjA_e1OJC9moxMvpLrHdLZDzCXHjBYi0XJ_1lowmuO_0iPEuPqN5AFpDV1ywmzJvGmMCMtw3A2wuN7hhem9OsWbwe6lzdwrefZLipyo4GZtIkg5ZdwGuHzm33zsM-X5gl_Ns4P6axHKiorNSR6nTAyA6B32YVET_FAM2YJQrXqpwA61wn1XLfarZqpdIQyJ5cgyiC33BFBlUL3lcRXLMLeYe6TjYGeV4K63qARCjM9yeOlsRbbW5ViWeGtR2Yf18pN8ysPXdbaXm_P_IVhl3jCTDJt9ctPh6pUCbkt36FZqO9A"; + + do_test( + KEY, + expect![[r#" + License { + sub: "rw-test", + iss: Test, + tier: Free, + exp: 9999999999, + } + "#]], + ); + } + + #[test] + fn test_empty_license_key() { + // Default license will be used. + do_test( + "", + expect![[r#" + License { + sub: "default", + iss: Prod, + tier: Free, + exp: 18446744073709551615, + } + "#]], + ); + } + + #[test] + fn test_invalid_license_key() { + const KEY: &str = "invalid"; + + do_test(KEY, expect!["invalid license key: InvalidToken"]); + } + + #[test] + fn test_expired_license_key() { + // "exp": 0 + const KEY: &str = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.\ + eyJzdWIiOiJydy10ZXN0IiwidGllciI6InBhaWQiLCJpc3MiOiJ0ZXN0LnJpc2luZ3dhdmUuY29tIiwiZXhwIjowfQ.\ + TyYmoT5Gw9-FN7DWDbeg3myW8g_3Xlc90i4M9bGuPf2WLv9zRMJy2r9J7sl1BO7t6F1uGgyrvNxsVRVZ2XF_WAs6uNlluYBnd4Cqvsj6Xny1XJCCo8II3RIea-ZlRjp6tc1saaoe-_eTtqDH8NIIWe73vVtBeBTBU4zAiN2vCtU_Si2XuoTLBKJMIjtn0HjLNhb6-DX2P3SCzp75tMyWzr49qcsBgratyKdu_v2kqBM1qw_dTaRg2ZeNNO6scSOBwu4YHHJTL4nUaZO2yEodI_OKUztIPLYuO2A33Fb5OE57S7LTgSzmxZLf7e23Vrck7Os14AfBQr7p9ncUeyIXhA"; + + do_test(KEY, expect!["invalid license key: ExpiredSignature"]); + } + + #[test] + fn test_invalid_issuer() { + // "iss": "bad.risingwave.com" + const KEY: &str = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.\ + eyJzdWIiOiJydy10ZXN0IiwidGllciI6ImZyZWUiLCJpc3MiOiJiYWQucmlzaW5nd2F2ZS5jb20iLCJleHAiOjk5OTk5OTk5OTl9.\ + SUbDJTri902FbGgIoe5L3LG4edTXoR42BQCIu_KLyW41OK47bMnD2aK7JggyJmWyGtN7b_596hxM9HjU58oQtHePUo_zHi5li5IcRaMi8gqHae7CJGqOGAUo9vYOWCP5OjEuDfozJhpgcHBLzDRnSwYnWhLKtsrzb3UcpOXEqRVK7EDShBNx6kNqfYs2LlFI7ASsgFRLhoRuOTR5LeVDjj6NZfkZGsdMe1VyrODWoGT9kcAF--hBpUd1ZJ5mZ67A0_948VPFBYDbDPcTRnw1-5MvdibO-jKX49rJ0rlPXcAbqKPE_yYUaqUaORUzb3PaPgCT_quO9PWPuAFIgAb_fg"; + + do_test(KEY, expect!["invalid license key: InvalidIssuer"]); + } + + #[test] + fn test_invalid_signature() { + const KEY: &str = + "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.\ + eyJzdWIiOiJydy10ZXN0IiwidGllciI6ImZyZWUiLCJpc3MiOiJ0ZXN0LnJpc2luZ3dhdmUuY29tIiwiZXhwIjo5OTk5OTk5OTk5fQ.\ + InvalidSignatureoe5L3LG4edTXoR42BQCIu_KLyW41OK47bMnD2aK7JggyJmWyGtN7b_596hxM9HjU58oQtHePUo_zHi5li5IcRaMi8gqHae7CJGqOGAUo9vYOWCP5OjEuDfozJhpgcHBLzDRnSwYnWhLKtsrzb3UcpOXEqRVK7EDShBNx6kNqfYs2LlFI7ASsgFRLhoRuOTR5LeVDjj6NZfkZGsdMe1VyrODWoGT9kcAF--hBpUd1ZJ5mZ67A0_948VPFBYDbDPcTRnw1-5MvdibO-jKX49rJ0rlPXcAbqKPE_yYUaqUaORUzb3PaPgCT_quO9PWPuAFIgAb_fg"; + + do_test(KEY, expect!["invalid license key: InvalidSignature"]); + } +} diff --git a/src/meta/Cargo.toml b/src/meta/Cargo.toml index c43c43c0dc01f..ddae0c9c24626 100644 --- a/src/meta/Cargo.toml +++ b/src/meta/Cargo.toml @@ -14,13 +14,15 @@ ignored = ["workspace-hack"] normal = ["workspace-hack"] [dependencies] +aes-siv = "0.7" anyhow = "1" arc-swap = "1" assert_matches = "1" async-trait = "0.1" aws-config = { workspace = true } aws-sdk-ec2 = { workspace = true } -base64-url = { version = "2.0.0" } +base64-url = { version = "3.0.0" } +bincode = "1.3" bytes = { version = "1", features = ["serde"] } chrono = "0.4" clap = { workspace = true } @@ -31,6 +33,7 @@ either = "1" enum-as-inner = "0.6" etcd-client = { workspace = true } fail = "0.5" +flate2 = "1" function_name = "0.3.0" futures = { version = "0.3", default-features = false, features = ["alloc"] } hex = "0.4" diff --git a/src/meta/model_v2/migration/Cargo.toml b/src/meta/model_v2/migration/Cargo.toml index b99efe2b7b0a7..ac9cd1b0d157b 100644 --- a/src/meta/model_v2/migration/Cargo.toml +++ b/src/meta/model_v2/migration/Cargo.toml @@ -15,6 +15,8 @@ normal = ["workspace-hack"] [dependencies] async-std = { version = "1", features = ["attributes", "tokio1"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" uuid = { version = "1", features = ["v4"] } [dependencies.sea-orm-migration] diff --git a/src/meta/model_v2/migration/README.md b/src/meta/model_v2/migration/README.md index 527d4ba538071..d68a624ced8a7 100644 --- a/src/meta/model_v2/migration/README.md +++ b/src/meta/model_v2/migration/README.md @@ -9,11 +9,11 @@ > **DO NOT** modify already published migration files. ## How to run the migrator CLI -- Generate a new migration file +- Generate a new migration file, a database endpoint is required but not used. ```sh - cargo run -- generate MIGRATION_NAME + export DATABASE_URL=sqlite::memory:; cargo run -- generate MIGRATION_NAME ``` -- Apply all pending migrations for test purposes, `DATABASE_URL` required. +- Apply all pending migrations for test purposes, change `DATABASE_URL` to the actual database endpoint. ```sh cargo run ``` diff --git a/src/meta/model_v2/migration/src/lib.rs b/src/meta/model_v2/migration/src/lib.rs index 7feaf88788693..a835bf0b9e6e8 100644 --- a/src/meta/model_v2/migration/src/lib.rs +++ b/src/meta/model_v2/migration/src/lib.rs @@ -1,7 +1,7 @@ #![allow(clippy::enum_variant_names)] pub use sea_orm_migration::prelude::*; - +pub use sea_orm_migration::MigrationStatus; mod m20230908_072257_init; mod m20231008_020431_hummock; mod m20240304_074901_subscription; @@ -9,6 +9,12 @@ mod m20240410_082733_with_version_column_migration; mod m20240410_154406_session_params; mod m20240417_062305_subscription_internal_table_name; mod m20240418_142249_function_runtime; +mod m20240506_112555_subscription_partial_ckpt; +mod m20240525_090457_secret; +mod m20240617_070131_index_column_properties; +mod m20240618_072634_function_compressed_binary; +mod m20240702_080451_system_param_value; +mod m20240702_084927_unnecessary_fk; pub struct Migrator; @@ -23,6 +29,12 @@ impl MigratorTrait for Migrator { Box::new(m20240410_154406_session_params::Migration), Box::new(m20240417_062305_subscription_internal_table_name::Migration), Box::new(m20240418_142249_function_runtime::Migration), + Box::new(m20240506_112555_subscription_partial_ckpt::Migration), + Box::new(m20240525_090457_secret::Migration), + Box::new(m20240618_072634_function_compressed_binary::Migration), + Box::new(m20240617_070131_index_column_properties::Migration), + Box::new(m20240702_080451_system_param_value::Migration), + Box::new(m20240702_084927_unnecessary_fk::Migration), ] } } diff --git a/src/meta/model_v2/migration/src/m20230908_072257_init.rs b/src/meta/model_v2/migration/src/m20230908_072257_init.rs index 260344a8b4fd9..7840c40cd8f30 100644 --- a/src/meta/model_v2/migration/src/m20230908_072257_init.rs +++ b/src/meta/model_v2/migration/src/m20230908_072257_init.rs @@ -1222,7 +1222,7 @@ enum Function { } #[derive(DeriveIden)] -enum Object { +pub(crate) enum Object { Table, Oid, ObjType, diff --git a/src/meta/model_v2/migration/src/m20231008_020431_hummock.rs b/src/meta/model_v2/migration/src/m20231008_020431_hummock.rs index 99f4d701c6445..42fe0c424f99c 100644 --- a/src/meta/model_v2/migration/src/m20231008_020431_hummock.rs +++ b/src/meta/model_v2/migration/src/m20231008_020431_hummock.rs @@ -30,7 +30,11 @@ impl MigrationTrait for Migration { .not_null() .primary_key(), ) - .col(ColumnDef::new(CompactionTask::Task).binary().not_null()) + .col( + ColumnDef::new(CompactionTask::Task) + .blob(BlobSize::Long) + .not_null(), + ) .col( ColumnDef::new(CompactionTask::ContextId) .integer() @@ -50,7 +54,7 @@ impl MigrationTrait for Migration { .not_null() .primary_key(), ) - .col(ColumnDef::new(CompactionConfig::Config).binary()) + .col(ColumnDef::new(CompactionConfig::Config).blob(BlobSize::Long)) .to_owned(), ) .await?; @@ -65,7 +69,7 @@ impl MigrationTrait for Migration { .not_null() .primary_key(), ) - .col(ColumnDef::new(CompactionStatus::Status).binary()) + .col(ColumnDef::new(CompactionStatus::Status).blob(BlobSize::Long)) .to_owned(), ) .await?; @@ -138,7 +142,7 @@ impl MigrationTrait for Migration { .boolean() .not_null(), ) - .col(ColumnDef::new(HummockVersionDelta::FullVersionDelta).binary()) + .col(ColumnDef::new(HummockVersionDelta::FullVersionDelta).blob(BlobSize::Long)) .to_owned(), ) .await?; diff --git a/src/meta/model_v2/migration/src/m20240304_074901_subscription.rs b/src/meta/model_v2/migration/src/m20240304_074901_subscription.rs index 50564c9b211a6..244c6abe11604 100644 --- a/src/meta/model_v2/migration/src/m20240304_074901_subscription.rs +++ b/src/meta/model_v2/migration/src/m20240304_074901_subscription.rs @@ -37,6 +37,17 @@ impl MigrationTrait for Migration { .string() .not_null(), ) + .foreign_key( + &mut ForeignKey::create() + .name("FK_subscription_object_id") + .from(Subscription::Table, Subscription::SubscriptionId) + .to( + crate::m20230908_072257_init::Object::Table, + crate::m20230908_072257_init::Object::Oid, + ) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) .to_owned(), ) .await?; diff --git a/src/meta/model_v2/migration/src/m20240417_062305_subscription_internal_table_name.rs b/src/meta/model_v2/migration/src/m20240417_062305_subscription_internal_table_name.rs index a4c6f60928c91..2fadc64b447d8 100644 --- a/src/meta/model_v2/migration/src/m20240417_062305_subscription_internal_table_name.rs +++ b/src/meta/model_v2/migration/src/m20240417_062305_subscription_internal_table_name.rs @@ -10,7 +10,7 @@ impl MigrationTrait for Migration { MigrationTable::alter() .table(Subscription::Table) .add_column( - ColumnDef::new(Subscription::SubscriptionInternalTableName).integer(), + ColumnDef::new(Subscription::SubscriptionInternalTableName).string(), ) .to_owned(), ) diff --git a/src/meta/model_v2/migration/src/m20240506_112555_subscription_partial_ckpt.rs b/src/meta/model_v2/migration/src/m20240506_112555_subscription_partial_ckpt.rs new file mode 100644 index 0000000000000..715be0e7cd23f --- /dev/null +++ b/src/meta/model_v2/migration/src/m20240506_112555_subscription_partial_ckpt.rs @@ -0,0 +1,124 @@ +use sea_orm_migration::prelude::{Table as MigrationTable, *}; + +use crate::drop_tables; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // drop tables cascade. + drop_tables!(manager, Subscription); + manager + .create_table( + MigrationTable::create() + .table(Subscription::Table) + .col( + ColumnDef::new(Subscription::SubscriptionId) + .integer() + .primary_key(), + ) + .col(ColumnDef::new(Subscription::Name).string().not_null()) + .col(ColumnDef::new(Subscription::Definition).string().not_null()) + .col( + ColumnDef::new(Subscription::RetentionSeconds) + .string() + .big_integer(), + ) + .col( + ColumnDef::new(Subscription::SubscriptionState) + .string() + .integer(), + ) + .col( + ColumnDef::new(Subscription::DependentTableId) + .integer() + .not_null(), + ) + .foreign_key( + &mut ForeignKey::create() + .name("FK_subscription_object_id") + .from(Subscription::Table, Subscription::SubscriptionId) + .to( + crate::m20230908_072257_init::Object::Table, + crate::m20230908_072257_init::Object::Oid, + ) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .to_owned(), + ) + .await?; + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // drop tables cascade. + drop_tables!(manager, Subscription); + manager + .create_table( + MigrationTable::create() + .table(Subscription::Table) + .col( + ColumnDef::new(Subscription::SubscriptionId) + .integer() + .primary_key(), + ) + .col(ColumnDef::new(Subscription::Name).string().not_null()) + .col(ColumnDef::new(Subscription::Definition).string().not_null()) + .col(ColumnDef::new(Subscription::Columns).binary().not_null()) + .col(ColumnDef::new(Subscription::PlanPk).binary().not_null()) + .col( + ColumnDef::new(Subscription::DistributionKey) + .json_binary() + .not_null(), + ) + .col( + ColumnDef::new(Subscription::Properties) + .json_binary() + .not_null(), + ) + .col( + ColumnDef::new(Subscription::SubscriptionFromName) + .string() + .not_null(), + ) + .col(ColumnDef::new(Subscription::SubscriptionInternalTableName).string()) + .foreign_key( + &mut ForeignKey::create() + .name("FK_subscription_object_id") + .from(Subscription::Table, Subscription::SubscriptionId) + .to( + crate::m20230908_072257_init::Object::Table, + crate::m20230908_072257_init::Object::Oid, + ) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .to_owned(), + ) + .await?; + Ok(()) + } +} + +#[derive(DeriveIden)] +enum Subscription { + Table, + // common + SubscriptionId, + Name, + Definition, + // before + Columns, + PlanPk, + DistributionKey, + Properties, + SubscriptionFromName, + SubscriptionInternalTableName, + // after + RetentionSeconds, + SubscriptionState, + DependentTableId, +} diff --git a/src/meta/model_v2/migration/src/m20240525_090457_secret.rs b/src/meta/model_v2/migration/src/m20240525_090457_secret.rs new file mode 100644 index 0000000000000..ed23085c66574 --- /dev/null +++ b/src/meta/model_v2/migration/src/m20240525_090457_secret.rs @@ -0,0 +1,103 @@ +use sea_orm_migration::prelude::{Table as MigrationTable, *}; + +use crate::{assert_not_has_tables, drop_tables}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + assert_not_has_tables!(manager, Secret); + manager + .create_table( + MigrationTable::create() + .table(Secret::Table) + .if_not_exists() + .col( + ColumnDef::new(Secret::SecretId) + .integer() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Secret::Name).string().not_null()) + .col(ColumnDef::new(Secret::Value).binary().not_null()) + .foreign_key( + &mut ForeignKey::create() + .name("FK_secret_object_id") + .from(Secret::Table, Secret::SecretId) + .to( + crate::m20230908_072257_init::Object::Table, + crate::m20230908_072257_init::Object::Oid, + ) + .on_delete(ForeignKeyAction::Cascade) + .to_owned(), + ) + .to_owned(), + ) + .await?; + + // Add a new column to the `sink` table + manager + .alter_table( + MigrationTable::alter() + .table(Sink::Table) + .add_column(ColumnDef::new(Sink::SecretRef).binary()) + .to_owned(), + ) + .await?; + + // Add a new column to the `source` table + manager + .alter_table( + MigrationTable::alter() + .table(Source::Table) + .add_column(ColumnDef::new(Source::SecretRef).binary()) + .to_owned(), + ) + .await?; + + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + drop_tables!(manager, Secret); + manager + .alter_table( + MigrationTable::alter() + .table(Sink::Table) + .drop_column(Sink::SecretRef) + .to_owned(), + ) + .await?; + manager + .alter_table( + MigrationTable::alter() + .table(Source::Table) + .drop_column(Source::SecretRef) + .to_owned(), + ) + .await?; + Ok(()) + } +} + +#[derive(DeriveIden)] +enum Secret { + Table, + SecretId, + Name, + Value, +} + +#[derive(DeriveIden)] +enum Sink { + Table, + SecretRef, +} + +#[derive(DeriveIden)] +enum Source { + Table, + SecretRef, +} diff --git a/src/meta/model_v2/migration/src/m20240617_070131_index_column_properties.rs b/src/meta/model_v2/migration/src/m20240617_070131_index_column_properties.rs new file mode 100644 index 0000000000000..daff755da999f --- /dev/null +++ b/src/meta/model_v2/migration/src/m20240617_070131_index_column_properties.rs @@ -0,0 +1,35 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Index::Table) + .add_column(ColumnDef::new(Index::IndexColumnProperties).binary()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .alter_table( + Table::alter() + .table(Index::Table) + .drop_column(Index::IndexColumnProperties) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum Index { + Table, + IndexColumnProperties, +} diff --git a/src/meta/model_v2/migration/src/m20240618_072634_function_compressed_binary.rs b/src/meta/model_v2/migration/src/m20240618_072634_function_compressed_binary.rs new file mode 100644 index 0000000000000..6b4ef6157bcf6 --- /dev/null +++ b/src/meta/model_v2/migration/src/m20240618_072634_function_compressed_binary.rs @@ -0,0 +1,74 @@ +use sea_orm_migration::prelude::*; + +use crate::sea_orm::{DatabaseBackend, DbBackend, Statement}; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Fix mismatch column `compressed_binary` type and do data migration + match manager.get_database_backend() { + DbBackend::MySql => { + // Creating function with compressed binary will fail in previous version, so we can + // safely assume that the column is always empty and we can just modify the column type + // without any data migration. + manager + .alter_table( + Table::alter() + .table(Function::Table) + .modify_column( + ColumnDef::new(Function::CompressedBinary).blob(BlobSize::Medium), + ) + .to_owned(), + ) + .await?; + } + DbBackend::Postgres => { + manager.get_connection().execute(Statement::from_string( + DatabaseBackend::Postgres, + "ALTER TABLE function ALTER COLUMN compressed_binary TYPE bytea USING compressed_binary::bytea", + )).await?; + } + DbBackend::Sqlite => { + // Sqlite does not support modifying column type, so we need to do data migration and column renaming. + // Note that: all these DDLs are not transactional, so if some of them fail, we need to manually run it again. + let conn = manager.get_connection(); + conn.execute(Statement::from_string( + DatabaseBackend::Sqlite, + "ALTER TABLE function ADD COLUMN compressed_binary_new BLOB", + )) + .await?; + conn.execute(Statement::from_string( + DatabaseBackend::Sqlite, + "UPDATE function SET compressed_binary_new = compressed_binary", + )) + .await?; + conn.execute(Statement::from_string( + DatabaseBackend::Sqlite, + "ALTER TABLE function DROP COLUMN compressed_binary", + )) + .await?; + conn.execute(Statement::from_string( + DatabaseBackend::Sqlite, + "ALTER TABLE function RENAME COLUMN compressed_binary_new TO compressed_binary", + )) + .await?; + } + } + + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + // DO nothing, the operations in `up` are idempotent and required to fix the column type mismatch. + Ok(()) + } +} + +#[derive(DeriveIden)] +enum Function { + Table, + CompressedBinary, +} diff --git a/src/meta/model_v2/migration/src/m20240702_080451_system_param_value.rs b/src/meta/model_v2/migration/src/m20240702_080451_system_param_value.rs new file mode 100644 index 0000000000000..ed032cb304e70 --- /dev/null +++ b/src/meta/model_v2/migration/src/m20240702_080451_system_param_value.rs @@ -0,0 +1,43 @@ +use sea_orm_migration::prelude::*; + +use crate::sea_orm::DbBackend; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + if manager.get_database_backend() == DbBackend::MySql { + manager + .alter_table( + Table::alter() + .table(SystemParameter::Table) + .modify_column(ColumnDef::new(SystemParameter::Value).text().not_null()) + .to_owned(), + ) + .await?; + } + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + if manager.get_database_backend() == DbBackend::MySql { + manager + .alter_table( + Table::alter() + .table(SystemParameter::Table) + .modify_column(ColumnDef::new(SystemParameter::Value).string().not_null()) + .to_owned(), + ) + .await?; + } + Ok(()) + } +} + +#[derive(DeriveIden)] +enum SystemParameter { + Table, + Value, +} diff --git a/src/meta/model_v2/migration/src/m20240702_084927_unnecessary_fk.rs b/src/meta/model_v2/migration/src/m20240702_084927_unnecessary_fk.rs new file mode 100644 index 0000000000000..f19f20c8c475a --- /dev/null +++ b/src/meta/model_v2/migration/src/m20240702_084927_unnecessary_fk.rs @@ -0,0 +1,37 @@ +use sea_orm_migration::prelude::*; + +use crate::sea_orm::DbBackend; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + if manager.get_database_backend() == DbBackend::MySql { + manager + .alter_table( + Table::alter() + .table(Alias::new("sink")) + .drop_foreign_key(Alias::new("FK_sink_connection_id")) + .drop_foreign_key(Alias::new("FK_sink_target_table_id")) + .to_owned(), + ) + .await?; + manager + .alter_table( + Table::alter() + .table(Alias::new("source")) + .drop_foreign_key(Alias::new("FK_source_connection_id")) + .to_owned(), + ) + .await?; + } + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + // Do nothing. + Ok(()) + } +} diff --git a/src/meta/model_v2/src/actor.rs b/src/meta/model_v2/src/actor.rs index 2ebafd23a51ad..c75eac7dbc4cf 100644 --- a/src/meta/model_v2/src/actor.rs +++ b/src/meta/model_v2/src/actor.rs @@ -14,12 +14,13 @@ use risingwave_pb::meta::table_fragments::actor_status::PbActorState; use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{ ActorId, ActorUpstreamActors, ConnectorSplits, ExprContext, FragmentId, VnodeBitmap, WorkerId, }; -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum ActorStatus { #[sea_orm(string_value = "INACTIVE")] @@ -47,7 +48,7 @@ impl From for PbActorState { } } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "actor")] pub struct Model { #[sea_orm(primary_key)] diff --git a/src/meta/model_v2/src/actor_dispatcher.rs b/src/meta/model_v2/src/actor_dispatcher.rs index 1d6a50665b12a..81211cc572701 100644 --- a/src/meta/model_v2/src/actor_dispatcher.rs +++ b/src/meta/model_v2/src/actor_dispatcher.rs @@ -14,10 +14,11 @@ use risingwave_pb::stream_plan::{PbDispatcher, PbDispatcherType}; use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{ActorId, ActorMapping, FragmentId, I32Array}; -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Deserialize, Serialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum DispatcherType { #[sea_orm(string_value = "HASH")] @@ -81,7 +82,7 @@ impl From for PbDispatcher { } } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize, Serialize)] #[sea_orm(table_name = "actor_dispatcher")] pub struct Model { #[sea_orm(primary_key)] diff --git a/src/meta/model_v2/src/catalog_version.rs b/src/meta/model_v2/src/catalog_version.rs index 53c6f9109635c..cfcd90c12f6a0 100644 --- a/src/meta/model_v2/src/catalog_version.rs +++ b/src/meta/model_v2/src/catalog_version.rs @@ -13,8 +13,9 @@ // limitations under the License. use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum VersionCategory { #[sea_orm(string_value = "NOTIFICATION")] @@ -23,7 +24,7 @@ pub enum VersionCategory { TableRevision, } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "catalog_version")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/cluster.rs b/src/meta/model_v2/src/cluster.rs index 767094f1a1e14..39d0bc3439980 100644 --- a/src/meta/model_v2/src/cluster.rs +++ b/src/meta/model_v2/src/cluster.rs @@ -13,8 +13,9 @@ // limitations under the License. use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "cluster")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/compaction_task.rs b/src/meta/model_v2/src/compaction_task.rs index 074fe9af450ea..302bd15744f28 100644 --- a/src/meta/model_v2/src/compaction_task.rs +++ b/src/meta/model_v2/src/compaction_task.rs @@ -14,7 +14,6 @@ use risingwave_pb::hummock::{CompactTask as PbCompactTask, CompactTaskAssignment}; use sea_orm::entity::prelude::*; -use serde::{Deserialize, Serialize}; use crate::{CompactionTaskId, WorkerId}; diff --git a/src/meta/model_v2/src/connection.rs b/src/meta/model_v2/src/connection.rs index 0e513e7061fd6..a6cfa4aefb58c 100644 --- a/src/meta/model_v2/src/connection.rs +++ b/src/meta/model_v2/src/connection.rs @@ -16,10 +16,11 @@ use risingwave_pb::catalog::connection::PbInfo; use risingwave_pb::catalog::PbConnection; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::{ConnectionId, PrivateLinkService}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "connection")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/database.rs b/src/meta/model_v2/src/database.rs index f8951f63c9681..089a687129696 100644 --- a/src/meta/model_v2/src/database.rs +++ b/src/meta/model_v2/src/database.rs @@ -15,10 +15,11 @@ use risingwave_pb::catalog::PbDatabase; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::DatabaseId; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "database")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/fragment.rs b/src/meta/model_v2/src/fragment.rs index af1d529a05980..7f69584538593 100644 --- a/src/meta/model_v2/src/fragment.rs +++ b/src/meta/model_v2/src/fragment.rs @@ -14,10 +14,11 @@ use risingwave_pb::meta::table_fragments::fragment::PbFragmentDistributionType; use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{FragmentId, FragmentVnodeMapping, I32Array, ObjectId, StreamNode}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "fragment")] pub struct Model { #[sea_orm(primary_key)] @@ -31,7 +32,7 @@ pub struct Model { pub upstream_fragment_id: I32Array, } -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum DistributionType { #[sea_orm(string_value = "SINGLE")] diff --git a/src/meta/model_v2/src/function.rs b/src/meta/model_v2/src/function.rs index 520df5cd2bf05..5589c1daa023f 100644 --- a/src/meta/model_v2/src/function.rs +++ b/src/meta/model_v2/src/function.rs @@ -16,10 +16,11 @@ use risingwave_pb::catalog::function::Kind; use risingwave_pb::catalog::PbFunction; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::{DataType, DataTypeArray, FunctionId}; -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum FunctionKind { #[sea_orm(string_value = "Scalar")] @@ -30,7 +31,7 @@ pub enum FunctionKind { Aggregate, } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "function")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/hummock_sequence.rs b/src/meta/model_v2/src/hummock_sequence.rs index 63cfe6592458b..58156c33266f7 100644 --- a/src/meta/model_v2/src/hummock_sequence.rs +++ b/src/meta/model_v2/src/hummock_sequence.rs @@ -13,13 +13,13 @@ // limitations under the License. use sea_orm::entity::prelude::*; - +use serde::{Deserialize, Serialize}; pub const COMPACTION_TASK_ID: &str = "compaction_task"; pub const COMPACTION_GROUP_ID: &str = "compaction_group"; pub const SSTABLE_OBJECT_ID: &str = "sstable_object"; pub const META_BACKUP_ID: &str = "meta_backup"; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Default, Serialize, Deserialize)] #[sea_orm(table_name = "hummock_sequence")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/hummock_version_delta.rs b/src/meta/model_v2/src/hummock_version_delta.rs index ae69f5eca9a1d..317e3c7199863 100644 --- a/src/meta/model_v2/src/hummock_version_delta.rs +++ b/src/meta/model_v2/src/hummock_version_delta.rs @@ -14,7 +14,6 @@ use risingwave_pb::hummock::PbHummockVersionDelta; use sea_orm::entity::prelude::*; -use serde::{Deserialize, Serialize}; use crate::{Epoch, HummockVersionId}; diff --git a/src/meta/model_v2/src/index.rs b/src/meta/model_v2/src/index.rs index 8f6dd60d6d76d..a291e325b24eb 100644 --- a/src/meta/model_v2/src/index.rs +++ b/src/meta/model_v2/src/index.rs @@ -15,10 +15,11 @@ use risingwave_pb::catalog::PbIndex; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; -use crate::{ExprNodeArray, IndexId, TableId}; +use crate::{ExprNodeArray, IndexColumnPropertiesArray, IndexId, TableId}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "index")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] @@ -27,6 +28,7 @@ pub struct Model { pub index_table_id: TableId, pub primary_table_id: TableId, pub index_items: ExprNodeArray, + pub index_column_properties: IndexColumnPropertiesArray, pub index_columns_len: i32, } @@ -75,6 +77,7 @@ impl From for ActiveModel { primary_table_id: Set(pb_index.primary_table_id as _), index_items: Set(pb_index.index_item.into()), index_columns_len: Set(pb_index.index_columns_len as _), + index_column_properties: Set(pb_index.index_column_properties.into()), } } } diff --git a/src/meta/model_v2/src/lib.rs b/src/meta/model_v2/src/lib.rs index 05fb29cb70bac..751ae99b64a17 100644 --- a/src/meta/model_v2/src/lib.rs +++ b/src/meta/model_v2/src/lib.rs @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use risingwave_pb::catalog::{PbCreateType, PbStreamJobStatus}; use risingwave_pb::meta::table_fragments::PbState as PbStreamJobState; +use risingwave_pb::secret::PbSecretRef; use risingwave_pb::stream_plan::PbStreamNode; use sea_orm::entity::prelude::*; use sea_orm::{DeriveActiveEnum, EnumIter, FromJsonQueryResult}; @@ -43,6 +44,8 @@ pub mod index; pub mod object; pub mod object_dependency; pub mod schema; +pub mod secret; +pub mod serde_seaql_migration; pub mod session_parameter; pub mod sink; pub mod source; @@ -71,6 +74,7 @@ pub type IndexId = ObjectId; pub type ViewId = ObjectId; pub type FunctionId = ObjectId; pub type ConnectionId = ObjectId; +pub type SecretId = ObjectId; pub type UserId = i32; pub type PrivilegeId = i32; @@ -83,7 +87,7 @@ pub type HummockSstableObjectId = i64; pub type FragmentId = i32; pub type ActorId = i32; -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum JobStatus { #[sea_orm(string_value = "INITIAL")] @@ -115,7 +119,7 @@ impl From for PbStreamJobState { } } -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum CreateType { #[sea_orm(string_value = "BACKGROUND")] @@ -170,7 +174,7 @@ macro_rules! derive_from_json_struct { /// Defines struct with a byte array that derives `DeriveValueType`, it will helps to map blob stored in database to Pb struct. macro_rules! derive_from_blob { ($struct_name:ident, $field_type:ty) => { - #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, DeriveValueType)] + #[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, DeriveValueType)] pub struct $struct_name(#[sea_orm] Vec); impl $struct_name { @@ -212,7 +216,7 @@ macro_rules! derive_from_blob { /// Defines struct with a byte array that derives `DeriveValueType`, it will helps to map blob stored in database to Pb struct array. macro_rules! derive_array_from_blob { ($struct_name:ident, $field_type:ty, $field_array_name:ident) => { - #[derive(Clone, PartialEq, Eq, DeriveValueType)] + #[derive(Clone, PartialEq, Eq, DeriveValueType, serde::Deserialize, serde::Serialize)] pub struct $struct_name(#[sea_orm] Vec); #[derive(Clone, PartialEq, ::prost::Message)] @@ -255,7 +259,56 @@ macro_rules! derive_array_from_blob { }; } -pub(crate) use {derive_array_from_blob, derive_from_blob, derive_from_json_struct}; +macro_rules! derive_btreemap_from_blob { + ($struct_name:ident, $key_type:ty, $value_type:ty, $field_type:ident) => { + #[derive(Clone, PartialEq, Eq, DeriveValueType, serde::Deserialize, serde::Serialize)] + pub struct $struct_name(#[sea_orm] Vec); + + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct $field_type { + #[prost(btree_map = "string, message")] + inner: BTreeMap<$key_type, $value_type>, + } + impl Eq for $field_type {} + + impl $struct_name { + pub fn to_protobuf(&self) -> BTreeMap<$key_type, $value_type> { + let data: $field_type = prost::Message::decode(self.0.as_slice()).unwrap(); + data.inner + } + + fn from_protobuf(val: BTreeMap<$key_type, $value_type>) -> Self { + Self(prost::Message::encode_to_vec(&$field_type { inner: val })) + } + } + + impl From> for $struct_name { + fn from(value: BTreeMap<$key_type, $value_type>) -> Self { + Self::from_protobuf(value) + } + } + + impl std::fmt::Debug for $struct_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_protobuf().fmt(f) + } + } + + impl Default for $struct_name { + fn default() -> Self { + Self(vec![]) + } + } + + impl sea_orm::sea_query::Nullable for $struct_name { + fn null() -> Value { + Value::Bytes(None) + } + } + }; +} + +pub(crate) use {derive_array_from_blob, derive_from_blob}; derive_from_json_struct!(I32Array, Vec); @@ -283,6 +336,8 @@ impl From>> for ActorUpstreamActors { } } +derive_btreemap_from_blob!(SecretRef, String, PbSecretRef, PbSecretRefMap); + derive_from_blob!(StreamNode, PbStreamNode); derive_from_blob!(DataType, risingwave_pb::data::PbDataType); derive_array_from_blob!( @@ -295,7 +350,7 @@ derive_array_from_blob!( risingwave_pb::plan_common::PbField, PbFieldArray ); -derive_from_json_struct!(Property, HashMap); +derive_from_json_struct!(Property, BTreeMap); derive_from_blob!(ColumnCatalog, risingwave_pb::plan_common::PbColumnCatalog); derive_array_from_blob!( ColumnCatalogArray, @@ -319,6 +374,11 @@ derive_array_from_blob!( risingwave_pb::common::PbColumnOrder, PbColumnOrderArray ); +derive_array_from_blob!( + IndexColumnPropertiesArray, + risingwave_pb::catalog::PbIndexColumnProperties, + PbIndexColumnPropertiesArray +); derive_from_blob!(SinkFormatDesc, risingwave_pb::catalog::PbSinkFormatDesc); derive_from_blob!(Cardinality, risingwave_pb::plan_common::PbCardinality); derive_from_blob!(TableVersion, risingwave_pb::catalog::table::PbTableVersion); diff --git a/src/meta/model_v2/src/object.rs b/src/meta/model_v2/src/object.rs index 6df5db623ae3c..4ee0e5a4f4c49 100644 --- a/src/meta/model_v2/src/object.rs +++ b/src/meta/model_v2/src/object.rs @@ -13,10 +13,11 @@ // limitations under the License. use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{DatabaseId, ObjectId, SchemaId, UserId}; -#[derive(Clone, Debug, PartialEq, Eq, Copy, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, Copy, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum ObjectType { #[sea_orm(string_value = "DATABASE")] @@ -39,6 +40,8 @@ pub enum ObjectType { Connection, #[sea_orm(string_value = "SUBSCRIPTION")] Subscription, + #[sea_orm(string_value = "SECRET")] + Secret, } impl ObjectType { @@ -54,11 +57,12 @@ impl ObjectType { ObjectType::Function => "function", ObjectType::Connection => "connection", ObjectType::Subscription => "subscription", + ObjectType::Secret => "secret", } } } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "object")] pub struct Model { #[sea_orm(primary_key)] @@ -101,6 +105,14 @@ pub enum Relation { on_delete = "Cascade" )] SelfRef1, + #[sea_orm( + belongs_to = "super::database::Entity", + from = "Column::DatabaseId", + to = "super::database::Column::DatabaseId", + on_update = "NoAction", + on_delete = "NoAction" + )] + Database2, #[sea_orm(has_many = "super::schema::Entity")] Schema, #[sea_orm(has_many = "super::sink::Entity")] diff --git a/src/meta/model_v2/src/object_dependency.rs b/src/meta/model_v2/src/object_dependency.rs index 0adaacfced3a9..d5ca89215a93d 100644 --- a/src/meta/model_v2/src/object_dependency.rs +++ b/src/meta/model_v2/src/object_dependency.rs @@ -13,10 +13,11 @@ // limitations under the License. use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{ObjectId, UserId}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "object_dependency")] pub struct Model { #[sea_orm(primary_key)] diff --git a/src/meta/model_v2/src/prelude.rs b/src/meta/model_v2/src/prelude.rs index 7d3c8cde7acb1..b17eae112aef0 100644 --- a/src/meta/model_v2/src/prelude.rs +++ b/src/meta/model_v2/src/prelude.rs @@ -32,6 +32,7 @@ pub use super::index::Entity as Index; pub use super::object::Entity as Object; pub use super::object_dependency::Entity as ObjectDependency; pub use super::schema::Entity as Schema; +pub use super::secret::Entity as Secret; pub use super::session_parameter::Entity as SessionParameter; pub use super::sink::Entity as Sink; pub use super::source::Entity as Source; diff --git a/src/meta/model_v2/src/schema.rs b/src/meta/model_v2/src/schema.rs index e1183d0bbbb2e..89c1c5c75205b 100644 --- a/src/meta/model_v2/src/schema.rs +++ b/src/meta/model_v2/src/schema.rs @@ -15,10 +15,11 @@ use risingwave_pb::catalog::PbSchema; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::SchemaId; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "schema")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/secret.rs b/src/meta/model_v2/src/secret.rs new file mode 100644 index 0000000000000..0d122267bb4bc --- /dev/null +++ b/src/meta/model_v2/src/secret.rs @@ -0,0 +1,58 @@ +// Copyright 2024 RisingWave Labs +// +// 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 risingwave_pb::catalog::PbSecret; +use sea_orm::entity::prelude::*; +use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "secret")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub secret_id: i32, + pub name: String, + #[sea_orm(column_type = "Binary(BlobSize::Blob(None))")] + pub value: Vec, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::object::Entity", + from = "Column::SecretId", + to = "super::object::Column::Oid", + on_update = "NoAction", + on_delete = "Cascade" + )] + Object, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Object.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +impl From for ActiveModel { + fn from(secret: PbSecret) -> Self { + Self { + secret_id: Set(secret.id as _), + name: Set(secret.name), + value: Set(secret.value), + } + } +} diff --git a/src/meta/model_v2/src/serde_seaql_migration.rs b/src/meta/model_v2/src/serde_seaql_migration.rs new file mode 100644 index 0000000000000..6cc75f57c0313 --- /dev/null +++ b/src/meta/model_v2/src/serde_seaql_migration.rs @@ -0,0 +1,31 @@ +// Copyright 2024 RisingWave Labs +// +// 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. + +/// It duplicates the one found in crate sea-orm-migration, but derives serde. +/// It's only used by metadata backup/restore. +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)] +// One should override the name of migration table via `MigratorTrait::migration_table_name` method +#[sea_orm(table_name = "seaql_migrations")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub version: String, + pub applied_at: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/meta/model_v2/src/session_parameter.rs b/src/meta/model_v2/src/session_parameter.rs index b0623270982f7..1fcb3f5bc2dae 100644 --- a/src/meta/model_v2/src/session_parameter.rs +++ b/src/meta/model_v2/src/session_parameter.rs @@ -14,7 +14,7 @@ use sea_orm::entity::prelude::*; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, serde::Deserialize, serde::Serialize)] #[sea_orm(table_name = "session_parameter")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/sink.rs b/src/meta/model_v2/src/sink.rs index ab7e869daee68..ad72aa3df981c 100644 --- a/src/meta/model_v2/src/sink.rs +++ b/src/meta/model_v2/src/sink.rs @@ -15,13 +15,14 @@ use risingwave_pb::catalog::{PbSink, PbSinkType}; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::{ - ColumnCatalogArray, ColumnOrderArray, ConnectionId, I32Array, Property, SinkFormatDesc, SinkId, - TableId, + ColumnCatalogArray, ColumnOrderArray, ConnectionId, I32Array, Property, SecretRef, + SinkFormatDesc, SinkId, TableId, }; -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum SinkType { #[sea_orm(string_value = "APPEND_ONLY")] @@ -53,7 +54,7 @@ impl From for SinkType { } } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "sink")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] @@ -71,6 +72,8 @@ pub struct Model { pub sink_from_name: String, pub sink_format_desc: Option, pub target_table: Option, + // `secret_ref` stores the mapping info mapping from property name to secret id and type. + pub secret_ref: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -126,6 +129,7 @@ impl From for ActiveModel { sink_from_name: Set(pb_sink.sink_from_name), sink_format_desc: Set(pb_sink.format_desc.as_ref().map(|x| x.into())), target_table: Set(pb_sink.target_table.map(|x| x as _)), + secret_ref: Set(Some(SecretRef::from(pb_sink.secret_refs))), } } } diff --git a/src/meta/model_v2/src/source.rs b/src/meta/model_v2/src/source.rs index 2b0e511e4afe6..9dd4bb5b1a11a 100644 --- a/src/meta/model_v2/src/source.rs +++ b/src/meta/model_v2/src/source.rs @@ -16,13 +16,14 @@ use risingwave_pb::catalog::source::OptionalAssociatedTableId; use risingwave_pb::catalog::PbSource; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::{ - ColumnCatalogArray, ConnectionId, I32Array, Property, SourceId, StreamSourceInfo, TableId, - WatermarkDescArray, + ColumnCatalogArray, ConnectionId, I32Array, Property, SecretRef, SourceId, StreamSourceInfo, + TableId, WatermarkDescArray, }; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "source")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] @@ -38,6 +39,8 @@ pub struct Model { pub optional_associated_table_id: Option, pub connection_id: Option, pub version: i64, + // `secret_ref` stores the mapping info mapping from property name to secret id and type. + pub secret_ref: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -100,6 +103,7 @@ impl From for ActiveModel { optional_associated_table_id: Set(optional_associated_table_id), connection_id: Set(source.connection_id.map(|id| id as _)), version: Set(source.version as _), + secret_ref: Set(Some(SecretRef::from(source.secret_refs))), } } } diff --git a/src/meta/model_v2/src/streaming_job.rs b/src/meta/model_v2/src/streaming_job.rs index bbb6ac332f2e0..1ab9225f43cda 100644 --- a/src/meta/model_v2/src/streaming_job.rs +++ b/src/meta/model_v2/src/streaming_job.rs @@ -13,10 +13,11 @@ // limitations under the License. use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{CreateType, JobStatus, StreamingParallelism}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "streaming_job")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/subscription.rs b/src/meta/model_v2/src/subscription.rs index 8a695c2b4c659..3d53c4b767804 100644 --- a/src/meta/model_v2/src/subscription.rs +++ b/src/meta/model_v2/src/subscription.rs @@ -15,22 +15,20 @@ use risingwave_pb::catalog::PbSubscription; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; -use crate::{ColumnCatalogArray, ColumnOrderArray, I32Array, Property, SubscriptionId}; +use crate::SubscriptionId; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "subscription")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub subscription_id: SubscriptionId, pub name: String, - pub columns: ColumnCatalogArray, - pub plan_pk: ColumnOrderArray, - pub distribution_key: I32Array, - pub properties: Property, + pub retention_seconds: i64, pub definition: String, - pub subscription_from_name: String, - pub subscription_internal_table_name: Option, + pub subscription_state: i32, + pub dependent_table_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -58,13 +56,10 @@ impl From for ActiveModel { Self { subscription_id: Set(pb_subscription.id as _), name: Set(pb_subscription.name), - columns: Set(pb_subscription.column_catalogs.into()), - plan_pk: Set(pb_subscription.plan_pk.into()), - distribution_key: Set(pb_subscription.distribution_key.into()), - properties: Set(pb_subscription.properties.into()), + retention_seconds: Set(pb_subscription.retention_seconds as _), definition: Set(pb_subscription.definition), - subscription_from_name: Set(pb_subscription.subscription_from_name), - subscription_internal_table_name: Set(pb_subscription.subscription_internal_table_name), + subscription_state: Set(pb_subscription.subscription_state), + dependent_table_id: Set(pb_subscription.dependent_table_id as _), } } } diff --git a/src/meta/model_v2/src/system_parameter.rs b/src/meta/model_v2/src/system_parameter.rs index f8447c57ab7a6..4e76e8aae7f96 100644 --- a/src/meta/model_v2/src/system_parameter.rs +++ b/src/meta/model_v2/src/system_parameter.rs @@ -13,8 +13,8 @@ // limitations under the License. use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +use serde::{Deserialize, Serialize}; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "system_parameter")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/table.rs b/src/meta/model_v2/src/table.rs index 974f1c8defacd..75fb66fcb2a5e 100644 --- a/src/meta/model_v2/src/table.rs +++ b/src/meta/model_v2/src/table.rs @@ -18,13 +18,16 @@ use risingwave_pb::catalog::{PbHandleConflictBehavior, PbTable}; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; use sea_orm::NotSet; +use serde::{Deserialize, Serialize}; use crate::{ Cardinality, ColumnCatalogArray, ColumnOrderArray, FragmentId, I32Array, ObjectId, SourceId, TableId, TableVersion, }; -#[derive(Clone, Debug, PartialEq, Hash, Copy, Eq, EnumIter, DeriveActiveEnum)] +#[derive( + Clone, Debug, PartialEq, Hash, Copy, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize, +)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum TableType { #[sea_orm(string_value = "TABLE")] @@ -60,7 +63,7 @@ impl From for TableType { } } -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum HandleConflictBehavior { #[sea_orm(string_value = "OVERWRITE")] @@ -98,7 +101,7 @@ impl From for HandleConflictBehavior { } } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "table")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/user.rs b/src/meta/model_v2/src/user.rs index f238683a83b02..7991d4f135881 100644 --- a/src/meta/model_v2/src/user.rs +++ b/src/meta/model_v2/src/user.rs @@ -16,10 +16,11 @@ use risingwave_pb::user::PbUserInfo; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; use sea_orm::NotSet; +use serde::{Deserialize, Serialize}; use crate::{AuthInfo, UserId}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "user")] pub struct Model { #[sea_orm(primary_key)] diff --git a/src/meta/model_v2/src/user_privilege.rs b/src/meta/model_v2/src/user_privilege.rs index 0ce0b78242cc7..48f9a38a5504c 100644 --- a/src/meta/model_v2/src/user_privilege.rs +++ b/src/meta/model_v2/src/user_privilege.rs @@ -14,10 +14,11 @@ use risingwave_pb::user::grant_privilege::PbAction; use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{ObjectId, PrivilegeId, UserId}; -#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum Action { #[sea_orm(string_value = "INSERT")] @@ -69,7 +70,7 @@ impl From for PbAction { } } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "user_privilege")] pub struct Model { #[sea_orm(primary_key)] diff --git a/src/meta/model_v2/src/view.rs b/src/meta/model_v2/src/view.rs index 0e32cd2275150..54783f8ac387a 100644 --- a/src/meta/model_v2/src/view.rs +++ b/src/meta/model_v2/src/view.rs @@ -15,10 +15,11 @@ use risingwave_pb::catalog::PbView; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::{FieldArray, Property, ViewId}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "view")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/model_v2/src/worker.rs b/src/meta/model_v2/src/worker.rs index 8694bb1c6d20c..ee2ab9b22d3c8 100644 --- a/src/meta/model_v2/src/worker.rs +++ b/src/meta/model_v2/src/worker.rs @@ -16,10 +16,11 @@ use risingwave_pb::common::worker_node::PbState; use risingwave_pb::common::{PbWorkerNode, PbWorkerType}; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; +use serde::{Deserialize, Serialize}; use crate::{TransactionId, WorkerId}; -#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum WorkerType { #[sea_orm(string_value = "FRONTEND")] @@ -59,7 +60,7 @@ impl From for PbWorkerType { } } -#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] #[sea_orm(rs_type = "String", db_type = "String(None)")] pub enum WorkerStatus { #[sea_orm(string_value = "STARTING")] @@ -101,7 +102,7 @@ impl From<&PbWorkerNode> for ActiveModel { } } -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "worker")] pub struct Model { #[sea_orm(primary_key)] diff --git a/src/meta/model_v2/src/worker_property.rs b/src/meta/model_v2/src/worker_property.rs index a206043532821..3ab8d411c8b5c 100644 --- a/src/meta/model_v2/src/worker_property.rs +++ b/src/meta/model_v2/src/worker_property.rs @@ -13,10 +13,11 @@ // limitations under the License. use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use crate::{I32Array, WorkerId}; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "worker_property")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] diff --git a/src/meta/node/src/lib.rs b/src/meta/node/src/lib.rs index 0330f2af1de3d..a7600ba930a15 100644 --- a/src/meta/node/src/lib.rs +++ b/src/meta/node/src/lib.rs @@ -76,7 +76,19 @@ pub struct MetaNodeOpts { /// Endpoint of the SQL service, make it non-option when SQL service is required. #[clap(long, hide = true, env = "RW_SQL_ENDPOINT")] - pub sql_endpoint: Option, + pub sql_endpoint: Option>, + + /// Username of sql backend, required when meta backend set to MySQL or PostgreSQL. + #[clap(long, hide = true, env = "RW_SQL_USERNAME", default_value = "")] + pub sql_username: String, + + /// Password of sql backend, required when meta backend set to MySQL or PostgreSQL. + #[clap(long, hide = true, env = "RW_SQL_PASSWORD", default_value = "")] + pub sql_password: Secret, + + /// Database of sql backend, required when meta backend set to MySQL or PostgreSQL. + #[clap(long, hide = true, env = "RW_SQL_DATABASE", default_value = "")] + pub sql_database: String, /// The HTTP REST-API address of the Prometheus instance associated to this cluster. /// This address is used to serve `PromQL` queries to Prometheus. @@ -221,7 +233,41 @@ pub fn start(opts: MetaNodeOpts) -> Pin + Send>> { }, MetaBackend::Mem => MetaStoreBackend::Mem, MetaBackend::Sql => MetaStoreBackend::Sql { - endpoint: opts.sql_endpoint.expect("sql endpoint is required"), + endpoint: opts + .sql_endpoint + .expect("sql endpoint is required") + .expose_secret() + .to_string(), + }, + MetaBackend::Sqlite => MetaStoreBackend::Sql { + endpoint: format!( + "sqlite://{}?mode=rwc", + opts.sql_endpoint + .expect("sql endpoint is required") + .expose_secret() + ), + }, + MetaBackend::Postgres => MetaStoreBackend::Sql { + endpoint: format!( + "postgres://{}:{}@{}/{}", + opts.sql_username, + opts.sql_password.expose_secret(), + opts.sql_endpoint + .expect("sql endpoint is required") + .expose_secret(), + opts.sql_database + ), + }, + MetaBackend::Mysql => MetaStoreBackend::Sql { + endpoint: format!( + "mysql://{}:{}@{}/{}", + opts.sql_username, + opts.sql_password.expose_secret(), + opts.sql_endpoint + .expect("sql endpoint is required") + .expose_secret(), + opts.sql_database + ), }, }; @@ -256,25 +302,26 @@ pub fn start(opts: MetaNodeOpts) -> Pin + Send>> { const MIN_TIMEOUT_INTERVAL_SEC: u64 = 20; let compaction_task_max_progress_interval_secs = { - (config - .storage - .object_store - .object_store_read_timeout_ms - .max(config.storage.object_store.object_store_upload_timeout_ms) - .max( - config - .storage - .object_store - .object_store_streaming_read_timeout_ms, - ) - .max( - config - .storage - .object_store - .object_store_streaming_upload_timeout_ms, - ) - .max(config.meta.compaction_task_max_progress_interval_secs * 1000)) - / 1000 + let retry_config = &config.storage.object_store.retry; + let max_streming_read_timeout_ms = (retry_config.streaming_read_attempt_timeout_ms + + retry_config.req_backoff_max_delay_ms) + * retry_config.streaming_read_retry_attempts as u64; + let max_streaming_upload_timeout_ms = (retry_config + .streaming_upload_attempt_timeout_ms + + retry_config.req_backoff_max_delay_ms) + * retry_config.streaming_upload_retry_attempts as u64; + let max_upload_timeout_ms = (retry_config.upload_attempt_timeout_ms + + retry_config.req_backoff_max_delay_ms) + * retry_config.upload_retry_attempts as u64; + let max_read_timeout_ms = (retry_config.read_attempt_timeout_ms + + retry_config.req_backoff_max_delay_ms) + * retry_config.read_retry_attempts as u64; + let max_timeout_ms = max_streming_read_timeout_ms + .max(max_upload_timeout_ms) + .max(max_streaming_upload_timeout_ms) + .max(max_read_timeout_ms) + .max(config.meta.compaction_task_max_progress_interval_secs * 1000); + max_timeout_ms / 1000 } + MIN_TIMEOUT_INTERVAL_SEC; let (mut join_handle, leader_lost_handle, shutdown_send) = rpc_serve( @@ -338,6 +385,12 @@ pub fn start(opts: MetaNodeOpts) -> Pin + Send>> { table_write_throughput_threshold: config.meta.table_write_throughput_threshold, min_table_split_write_throughput: config.meta.min_table_split_write_throughput, partition_vnode_count: config.meta.partition_vnode_count, + compact_task_table_size_partition_threshold_low: config + .meta + .compact_task_table_size_partition_threshold_low, + compact_task_table_size_partition_threshold_high: config + .meta + .compact_task_table_size_partition_threshold_high, do_not_config_object_storage_lifecycle: config .meta .do_not_config_object_storage_lifecycle, @@ -347,7 +400,7 @@ pub fn start(opts: MetaNodeOpts) -> Pin + Send>> { compaction_task_max_progress_interval_secs, compaction_config: Some(config.meta.compaction_config), cut_table_size_limit: config.meta.cut_table_size_limit, - hybird_partition_vnode_count: config.meta.hybird_partition_vnode_count, + hybrid_partition_node_count: config.meta.hybrid_partition_vnode_count, event_log_enabled: config.meta.event_log_enabled, event_log_channel_max_size: config.meta.event_log_channel_max_size, advertise_addr: opts.advertise_addr, @@ -363,6 +416,15 @@ pub fn start(opts: MetaNodeOpts) -> Pin + Send>> { .enable_check_task_level_overlap, enable_dropped_column_reclaim: config.meta.enable_dropped_column_reclaim, object_store_config: config.storage.object_store, + max_trivial_move_task_count_per_loop: config + .meta + .developer + .max_trivial_move_task_count_per_loop, + max_get_task_probe_times: config.meta.developer.max_get_task_probe_times, + secret_store_private_key: config.meta.secret_store_private_key, + table_info_statistic_history_times: config + .storage + .table_info_statistic_history_times, }, config.system.into_init_system_params(), Default::default(), diff --git a/src/meta/node/src/server.rs b/src/meta/node/src/server.rs index bf5bb72ed731b..57ccad6c9b5e6 100644 --- a/src/meta/node/src/server.rs +++ b/src/meta/node/src/server.rs @@ -25,7 +25,7 @@ use risingwave_common::monitor::connection::{RouterExt, TcpConfig}; use risingwave_common::session_config::SessionConfig; use risingwave_common::system_param::reader::SystemParamsRead; use risingwave_common::telemetry::manager::TelemetryManager; -use risingwave_common::telemetry::telemetry_env_enabled; +use risingwave_common::telemetry::{report_scarf_enabled, report_to_scarf, telemetry_env_enabled}; use risingwave_common_service::metrics_manager::MetricsManager; use risingwave_common_service::tracing::TracingExtractLayer; use risingwave_meta::barrier::StreamRpcManager; @@ -36,7 +36,6 @@ use risingwave_meta::rpc::intercept::MetricsMiddlewareLayer; use risingwave_meta::rpc::ElectionClientRef; use risingwave_meta::stream::ScaleController; use risingwave_meta::MetaStoreBackend; -use risingwave_meta_model_migration::{Migrator, MigratorTrait}; use risingwave_meta_service::backup_service::BackupServiceImpl; use risingwave_meta_service::cloud_service::CloudServiceImpl; use risingwave_meta_service::cluster_service::ClusterServiceImpl; @@ -408,13 +407,6 @@ pub async fn start_service_as_election_leader( mut svc_shutdown_rx: WatchReceiver<()>, ) -> MetaResult<()> { tracing::info!("Defining leader services"); - if let MetaStoreImpl::Sql(sql_store) = &meta_store_impl { - // Try to upgrade if any new model changes are added. - Migrator::up(&sql_store.conn, None) - .await - .expect("Failed to upgrade models in meta store"); - } - let env = MetaSrvEnv::new( opts.clone(), init_system_params, @@ -486,6 +478,7 @@ pub async fn start_service_as_election_leader( ) .await .unwrap(); + let object_store_media_type = hummock_manager.object_store_media_type(); let meta_member_srv = MetaMemberServiceImpl::new(match election_client.clone() { None => Either::Right(address_info.clone()), @@ -587,6 +580,11 @@ pub async fn start_service_as_election_leader( .unwrap(), ); + hummock_manager + .may_fill_backward_state_table_info() + .await + .unwrap(); + // Initialize services. let backup_manager = BackupManager::new( env.clone(), @@ -654,7 +652,7 @@ pub async fn start_service_as_election_leader( ); let health_srv = HealthServiceImpl::new(); let backup_srv = BackupServiceImpl::new(backup_manager); - let telemetry_srv = TelemetryInfoServiceImpl::new(env.meta_store_ref()); + let telemetry_srv = TelemetryInfoServiceImpl::new(env.meta_store()); let system_params_srv = SystemParamsServiceImpl::new(env.system_params_manager_impl_ref()); let session_params_srv = SessionParamsServiceImpl::new(env.session_params_manager_impl_ref()); let serving_srv = @@ -745,6 +743,7 @@ pub async fn start_service_as_election_leader( Arc::new(MetaReportCreator::new( metadata_manager.clone(), env.meta_store().backend(), + object_store_media_type, )), ); @@ -754,6 +753,11 @@ pub async fn start_service_as_election_leader( } else { tracing::info!("Telemetry didn't start due to meta backend or config"); } + if report_scarf_enabled() { + tokio::spawn(report_to_scarf()); + } else { + tracing::info!("Scarf reporting is disabled"); + }; if let Some(pair) = env.event_log_manager_ref().take_join_handle() { sub_tasks.push(pair); diff --git a/src/meta/service/src/cloud_service.rs b/src/meta/service/src/cloud_service.rs index b77b751b281e7..5777f1c405009 100644 --- a/src/meta/service/src/cloud_service.rs +++ b/src/meta/service/src/cloud_service.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use std::sync::LazyLock; use async_trait::async_trait; @@ -147,7 +147,6 @@ impl CloudService for CloudServiceImpl { } } // try fetch kafka metadata, return error message on failure - let source_cfg: HashMap = source_cfg.into_iter().collect(); let props = ConnectorProperties::extract(source_cfg, false); if let Err(e) = props { return Ok(new_rwc_validate_fail_response( diff --git a/src/meta/service/src/cluster_service.rs b/src/meta/service/src/cluster_service.rs index d69debbe90816..ef7295a6d5136 100644 --- a/src/meta/service/src/cluster_service.rs +++ b/src/meta/service/src/cluster_service.rs @@ -140,14 +140,17 @@ impl ClusterService for ClusterServiceImpl { ) -> Result, Status> { let req = request.into_inner(); let host = req.get_host()?.clone(); - match &self.metadata_manager { - MetadataManager::V1(mgr) => { - let _ = mgr.cluster_manager.delete_worker_node(host).await?; - } - MetadataManager::V2(mgr) => { - let _ = mgr.cluster_controller.delete_worker(host).await?; - } - } + + let worker_node = match &self.metadata_manager { + MetadataManager::V1(mgr) => mgr.cluster_manager.delete_worker_node(host).await?, + MetadataManager::V2(mgr) => mgr.cluster_controller.delete_worker(host).await?, + }; + tracing::info!( + host = ?worker_node.host, + id = worker_node.id, + r#type = ?worker_node.r#type(), + "deleted worker node", + ); Ok(Response::new(DeleteWorkerNodeResponse { status: None })) } diff --git a/src/meta/service/src/ddl_service.rs b/src/meta/service/src/ddl_service.rs index f5012e5796e69..2b2b1bcfeb2b3 100644 --- a/src/meta/service/src/ddl_service.rs +++ b/src/meta/service/src/ddl_service.rs @@ -25,7 +25,7 @@ use risingwave_pb::catalog::connection::private_link_service::{ }; use risingwave_pb::catalog::connection::PbPrivateLinkService; use risingwave_pb::catalog::table::OptionalAssociatedSourceId; -use risingwave_pb::catalog::{connection, Comment, Connection, CreateType}; +use risingwave_pb::catalog::{connection, Comment, Connection, CreateType, Secret}; use risingwave_pb::ddl_service::ddl_service_server::DdlService; use risingwave_pb::ddl_service::drop_table_request::PbSourceId; use risingwave_pb::ddl_service::*; @@ -82,6 +82,7 @@ impl DdlServiceImpl { } fn extract_replace_table_info(change: ReplaceTablePlan) -> ReplaceTableInfo { + let job_type = change.get_job_type().unwrap_or_default(); let mut source = change.source; let mut fragment_graph = change.fragment_graph.unwrap(); let mut table = change.table.unwrap(); @@ -89,19 +90,14 @@ impl DdlServiceImpl { table.optional_associated_source_id { source.as_mut().unwrap().id = source_id; - fill_table_stream_graph_info( - &mut source, - &mut table, - TableJobType::General, - &mut fragment_graph, - ); + fill_table_stream_graph_info(&mut source, &mut table, job_type, &mut fragment_graph); } let table_col_index_mapping = change .table_col_index_mapping .as_ref() .map(ColIndexMapping::from_protobuf); - let stream_job = StreamingJob::Table(source, table, TableJobType::General); + let stream_job = StreamingJob::Table(source, table, job_type); ReplaceTableInfo { streaming_job: stream_job, @@ -148,6 +144,41 @@ impl DdlService for DdlServiceImpl { })) } + async fn create_secret( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let pb_secret = Secret { + id: 0, + name: req.get_name().clone(), + database_id: req.get_database_id(), + value: req.get_value().clone(), + owner: req.get_owner_id(), + schema_id: req.get_schema_id(), + }; + let version = self + .ddl_controller + .run_command(DdlCommand::CreateSecret(pb_secret)) + .await?; + + Ok(Response::new(CreateSecretResponse { version })) + } + + async fn drop_secret( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let secret_id = req.get_secret_id(); + let version = self + .ddl_controller + .run_command(DdlCommand::DropSecret(secret_id)) + .await?; + + Ok(Response::new(DropSecretResponse { version })) + } + async fn create_schema( &self, request: Request, @@ -320,16 +351,7 @@ impl DdlService for DdlServiceImpl { let req = request.into_inner(); let subscription = req.get_subscription()?.clone(); - let fragment_graph = req.get_fragment_graph()?.clone(); - - let stream_job = StreamingJob::Subscription(subscription); - - let command = DdlCommand::CreateStreamingJob( - stream_job, - fragment_graph, - CreateType::Foreground, - None, - ); + let command = DdlCommand::CreateSubscription(subscription); let version = self.ddl_controller.run_command(command).await?; @@ -347,11 +369,7 @@ impl DdlService for DdlServiceImpl { let subscription_id = request.subscription_id; let drop_mode = DropMode::from_request_setting(request.cascade); - let command = DdlCommand::DropStreamingJob( - StreamingJobId::Subscription(subscription_id), - drop_mode, - None, - ); + let command = DdlCommand::DropSubscription(subscription_id, drop_mode); let version = self.ddl_controller.run_command(command).await?; diff --git a/src/meta/service/src/hummock_service.rs b/src/meta/service/src/hummock_service.rs index d2991d4a005d6..1e46438cb8ddd 100644 --- a/src/meta/service/src/hummock_service.rs +++ b/src/meta/service/src/hummock_service.rs @@ -666,6 +666,22 @@ impl HummockManagerService for HummockServiceImpl { let response = Response::new(CancelCompactTaskResponse { ret }); return Ok(response); } + + async fn list_change_log_epochs( + &self, + request: Request, + ) -> Result, Status> { + let ListChangeLogEpochsRequest { + table_id, + min_epoch, + max_count, + } = request.into_inner(); + let epochs = self + .hummock_manager + .list_change_log_epochs(table_id, min_epoch, max_count) + .await; + Ok(Response::new(ListChangeLogEpochsResponse { epochs })) + } } #[cfg(test)] diff --git a/src/meta/service/src/notification_service.rs b/src/meta/service/src/notification_service.rs index e9a5e4a017ad4..e4a8d298e0788 100644 --- a/src/meta/service/src/notification_service.rs +++ b/src/meta/service/src/notification_service.rs @@ -24,7 +24,7 @@ use risingwave_pb::hummock::WriteLimits; use risingwave_pb::meta::meta_snapshot::SnapshotVersion; use risingwave_pb::meta::notification_service_server::NotificationService; use risingwave_pb::meta::{ - FragmentParallelUnitMapping, GetSessionParamsResponse, MetaSnapshot, SubscribeRequest, + FragmentWorkerSlotMapping, GetSessionParamsResponse, MetaSnapshot, SubscribeRequest, SubscribeType, }; use risingwave_pb::user::UserInfo; @@ -80,6 +80,7 @@ impl NotificationServiceImpl { views, functions, connections, + secrets, ) = catalog_guard.database.get_catalog(); let users = catalog_guard.user.list_users(); let notification_version = self.env.notification_manager().current_version().await; @@ -95,6 +96,7 @@ impl NotificationServiceImpl { views, functions, connections, + secrets, ), users, notification_version, @@ -114,6 +116,7 @@ impl NotificationServiceImpl { views, functions, connections, + secrets, ), users, ) = catalog_guard.snapshot().await?; @@ -130,6 +133,7 @@ impl NotificationServiceImpl { views, functions, connections, + secrets, ), users, notification_version, @@ -138,9 +142,9 @@ impl NotificationServiceImpl { } } - async fn get_parallel_unit_mapping_snapshot( + async fn get_worker_slot_mapping_snapshot( &self, - ) -> MetaResult<(Vec, NotificationVersion)> { + ) -> MetaResult<(Vec, NotificationVersion)> { match &self.metadata_manager { MetadataManager::V1(mgr) => { let fragment_guard = mgr.fragment_manager.get_fragment_read_guard().await; @@ -161,11 +165,11 @@ impl NotificationServiceImpl { } } - fn get_serving_vnode_mappings(&self) -> Vec { + fn get_serving_vnode_mappings(&self) -> Vec { self.serving_vnode_mapping .all() .iter() - .map(|(fragment_id, mapping)| FragmentParallelUnitMapping { + .map(|(fragment_id, mapping)| FragmentWorkerSlotMapping { fragment_id: *fragment_id, mapping: Some(mapping.to_protobuf()), }) @@ -237,13 +241,16 @@ impl NotificationServiceImpl { views, functions, connections, + secrets, ), users, catalog_version, ) = self.get_catalog_snapshot().await?; - let (parallel_unit_mappings, parallel_unit_mapping_version) = - self.get_parallel_unit_mapping_snapshot().await?; - let serving_parallel_unit_mappings = self.get_serving_vnode_mappings(); + + let (streaming_worker_slot_mappings, streaming_worker_slot_mapping_version) = + self.get_worker_slot_mapping_snapshot().await?; + let serving_worker_slot_mappings = self.get_serving_vnode_mappings(); + let (nodes, worker_node_version) = self.get_worker_node_snapshot().await?; let hummock_snapshot = Some(self.hummock_manager.latest_snapshot()); @@ -269,16 +276,17 @@ impl NotificationServiceImpl { subscriptions, functions, connections, + secrets, users, - parallel_unit_mappings, nodes, hummock_snapshot, - serving_parallel_unit_mappings, version: Some(SnapshotVersion { catalog_version, - parallel_unit_mapping_version, worker_node_version, + streaming_worker_slot_mapping_version, }), + serving_worker_slot_mappings, + streaming_worker_slot_mappings, session_params, ..Default::default() }) diff --git a/src/meta/service/src/scale_service.rs b/src/meta/service/src/scale_service.rs index 273fdbca9bc07..49dabd5d88d23 100644 --- a/src/meta/service/src/scale_service.rs +++ b/src/meta/service/src/scale_service.rs @@ -22,8 +22,8 @@ use risingwave_meta_model_v2::FragmentId; use risingwave_pb::common::WorkerType; use risingwave_pb::meta::scale_service_server::ScaleService; use risingwave_pb::meta::{ - GetClusterInfoRequest, GetClusterInfoResponse, GetReschedulePlanRequest, - GetReschedulePlanResponse, Reschedule, RescheduleRequest, RescheduleResponse, + GetClusterInfoRequest, GetClusterInfoResponse, Reschedule, RescheduleRequest, + RescheduleResponse, }; use risingwave_pb::source::{ConnectorSplit, ConnectorSplits}; use tonic::{Request, Response, Status}; @@ -39,7 +39,6 @@ pub struct ScaleServiceImpl { source_manager: SourceManagerRef, stream_manager: GlobalStreamManagerRef, barrier_manager: BarrierManagerRef, - scale_controller: ScaleControllerRef, } impl ScaleServiceImpl { @@ -48,14 +47,13 @@ impl ScaleServiceImpl { source_manager: SourceManagerRef, stream_manager: GlobalStreamManagerRef, barrier_manager: BarrierManagerRef, - scale_controller: ScaleControllerRef, + _scale_controller: ScaleControllerRef, ) -> Self { Self { metadata_manager, source_manager, stream_manager, barrier_manager, - scale_controller, } } @@ -223,62 +221,4 @@ impl ScaleService for ScaleServiceImpl { revision: next_revision.into(), })) } - - #[cfg_attr(coverage, coverage(off))] - async fn get_reschedule_plan( - &self, - request: Request, - ) -> Result, Status> { - self.barrier_manager.check_status_running()?; - - let req = request.into_inner(); - - let _reschedule_job_lock = self.stream_manager.reschedule_lock_read_guard().await; - - let current_revision = self.get_revision().await; - - if req.revision != current_revision.inner() { - return Ok(Response::new(GetReschedulePlanResponse { - success: false, - revision: current_revision.inner(), - reschedules: Default::default(), - })); - } - - let policy = req - .policy - .ok_or_else(|| Status::invalid_argument("policy is required"))?; - - let scale_controller = &self.scale_controller; - - let plan = scale_controller.get_reschedule_plan(policy).await?; - - let next_revision = self.get_revision().await; - - // generate reschedule plan will not change the revision - assert_eq!(current_revision, next_revision); - - Ok(Response::new(GetReschedulePlanResponse { - success: true, - revision: next_revision.into(), - reschedules: plan - .into_iter() - .map(|(fragment_id, reschedule)| { - ( - fragment_id, - Reschedule { - added_parallel_units: reschedule - .added_parallel_units - .into_iter() - .collect(), - removed_parallel_units: reschedule - .removed_parallel_units - .into_iter() - .collect(), - }, - ) - }) - .collect(), - })) - } } diff --git a/src/meta/service/src/serving_service.rs b/src/meta/service/src/serving_service.rs index d1b013e078e0f..92c5fd9db4837 100644 --- a/src/meta/service/src/serving_service.rs +++ b/src/meta/service/src/serving_service.rs @@ -16,7 +16,7 @@ use itertools::Itertools; use risingwave_meta::manager::MetadataManager; use risingwave_pb::meta::serving_service_server::ServingService; use risingwave_pb::meta::{ - FragmentParallelUnitMapping, GetServingVnodeMappingsRequest, GetServingVnodeMappingsResponse, + FragmentWorkerSlotMapping, GetServingVnodeMappingsRequest, GetServingVnodeMappingsResponse, }; use tonic::{Request, Response, Status}; @@ -49,7 +49,7 @@ impl ServingService for ServingServiceImpl { .serving_vnode_mapping .all() .into_iter() - .map(|(fragment_id, mapping)| FragmentParallelUnitMapping { + .map(|(fragment_id, mapping)| FragmentWorkerSlotMapping { fragment_id, mapping: Some(mapping.to_protobuf()), }) @@ -78,8 +78,8 @@ impl ServingService for ServingServiceImpl { } }; Ok(Response::new(GetServingVnodeMappingsResponse { - mappings, fragment_to_table, + worker_slot_mappings: mappings, })) } } diff --git a/src/meta/service/src/session_config.rs b/src/meta/service/src/session_config.rs index a4a7d1b70591a..a3999a3d27426 100644 --- a/src/meta/service/src/session_config.rs +++ b/src/meta/service/src/session_config.rs @@ -19,7 +19,6 @@ use risingwave_pb::meta::{ GetSessionParamsRequest, GetSessionParamsResponse, SetSessionParamRequest, SetSessionParamResponse, }; -use serde_json; use thiserror_ext::AsReport; use tonic::{Request, Response, Status}; diff --git a/src/meta/service/src/stream_service.rs b/src/meta/service/src/stream_service.rs index cf9a8b1a3e48c..be520132b167f 100644 --- a/src/meta/service/src/stream_service.rs +++ b/src/meta/service/src/stream_service.rs @@ -108,7 +108,7 @@ impl StreamManagerService for StreamServiceImpl { let request = request.into_inner(); let actor_to_apply = match request.kind() { - ThrottleTarget::Source => { + ThrottleTarget::Source | ThrottleTarget::TableWithSource => { self.metadata_manager .update_source_rate_limit_by_source_id(request.id as SourceId, request.rate) .await? diff --git a/src/meta/service/src/user_service.rs b/src/meta/service/src/user_service.rs index d7dd4f5b4de4b..cab1b29228055 100644 --- a/src/meta/service/src/user_service.rs +++ b/src/meta/service/src/user_service.rs @@ -76,7 +76,7 @@ impl UserServiceImpl { } expanded_privileges.push(privilege); } - } else if let Some(Object::AllDmlTablesSchemaId(schema_id)) = &privilege.object { + } else if let Some(Object::AllDmlRelationsSchemaId(schema_id)) = &privilege.object { let tables = match &self.metadata_manager { MetadataManager::V1(mgr) => { mgr.catalog_manager.list_dml_table_ids(*schema_id).await @@ -89,6 +89,16 @@ impl UserServiceImpl { .map(|id| id as _) .collect(), }; + let views = match &self.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_view_ids(*schema_id).await, + MetadataManager::V2(mgr) => mgr + .catalog_controller + .list_view_ids(*schema_id as _) + .await? + .into_iter() + .map(|id| id as _) + .collect(), + }; for table_id in tables { let mut privilege = privilege.clone(); privilege.object = Some(Object::TableId(table_id)); @@ -100,6 +110,17 @@ impl UserServiceImpl { } expanded_privileges.push(privilege); } + for view_id in views { + let mut privilege = privilege.clone(); + privilege.object = Some(Object::ViewId(view_id)); + if let Some(true) = with_grant_option { + privilege + .action_with_opts + .iter_mut() + .for_each(|p| p.with_grant_option = true); + } + expanded_privileges.push(privilege); + } } else if let Some(Object::AllSourcesSchemaId(schema_id)) = &privilege.object { let sources = match &self.metadata_manager { MetadataManager::V1(mgr) => { diff --git a/src/meta/src/backup_restore/backup_manager.rs b/src/meta/src/backup_restore/backup_manager.rs index 6fdcb1d13ba3b..1b3dd61152278 100644 --- a/src/meta/src/backup_restore/backup_manager.rs +++ b/src/meta/src/backup_restore/backup_manager.rs @@ -31,11 +31,11 @@ use risingwave_pb::meta::subscribe_response::{Info, Operation}; use thiserror_ext::AsReport; use tokio::task::JoinHandle; -use crate::backup_restore::meta_snapshot_builder; use crate::backup_restore::metrics::BackupManagerMetrics; +use crate::backup_restore::{meta_snapshot_builder, meta_snapshot_builder_v2}; use crate::hummock::sequence::next_meta_backup_id; use crate::hummock::{HummockManagerRef, HummockVersionSafePoint}; -use crate::manager::{LocalNotification, MetaSrvEnv}; +use crate::manager::{LocalNotification, MetaSrvEnv, MetaStoreImpl}; use crate::rpc::metrics::MetaMetrics; use crate::MetaResult; @@ -345,23 +345,41 @@ impl BackupWorker { fn start(self, job_id: u64, remarks: Option) -> JoinHandle<()> { let backup_manager_clone = self.backup_manager.clone(); let job = async move { - let mut snapshot_builder = meta_snapshot_builder::MetaSnapshotV1Builder::new( - backup_manager_clone.env.meta_store().as_kv().clone(), - ); - // Reuse job id as snapshot id. let hummock_manager = backup_manager_clone.hummock_manager.clone(); - snapshot_builder - .build(job_id, async move { - hummock_manager.get_current_version().await - }) - .await?; - let snapshot = snapshot_builder.finish()?; - backup_manager_clone - .backup_store - .load() - .0 - .create(&snapshot, remarks) - .await?; + let hummock_version_builder = + async move { hummock_manager.get_current_version().await }; + match backup_manager_clone.env.meta_store() { + MetaStoreImpl::Kv(kv) => { + let mut snapshot_builder = + meta_snapshot_builder::MetaSnapshotV1Builder::new(kv.clone()); + // Reuse job id as snapshot id. + snapshot_builder + .build(job_id, hummock_version_builder) + .await?; + let snapshot = snapshot_builder.finish()?; + backup_manager_clone + .backup_store + .load() + .0 + .create(&snapshot, remarks) + .await?; + } + MetaStoreImpl::Sql(sql) => { + let mut snapshot_builder = + meta_snapshot_builder_v2::MetaSnapshotV2Builder::new(sql.clone()); + // Reuse job id as snapshot id. + snapshot_builder + .build(job_id, hummock_version_builder) + .await?; + let snapshot = snapshot_builder.finish()?; + backup_manager_clone + .backup_store + .load() + .0 + .create(&snapshot, remarks) + .await?; + } + } Ok(BackupJobResult::Succeeded) }; tokio::spawn(async move { @@ -383,7 +401,7 @@ async fn create_snapshot_store( &config.0, metric, "Meta Backup", - object_store_config.clone(), + Arc::new(object_store_config.clone()), ) .await, ); diff --git a/src/meta/src/backup_restore/meta_snapshot_builder.rs b/src/meta/src/backup_restore/meta_snapshot_builder.rs index 323f49b68663e..bb8a1eb919fdc 100644 --- a/src/meta/src/backup_restore/meta_snapshot_builder.rs +++ b/src/meta/src/backup_restore/meta_snapshot_builder.rs @@ -187,10 +187,8 @@ mod tests { let meta_store = MemStore::new(); let mut builder = MetaSnapshotBuilder::new(meta_store.clone()); - let hummock_version = HummockVersion { - id: 1, - ..Default::default() - }; + let mut hummock_version = HummockVersion::default(); + hummock_version.id = 1; let get_ckpt_builder = |v: &HummockVersion| { let v_ = v.clone(); async move { v_ } diff --git a/src/meta/src/backup_restore/meta_snapshot_builder_v2.rs b/src/meta/src/backup_restore/meta_snapshot_builder_v2.rs index 107db4d271194..9e4ad6a0c05a1 100644 --- a/src/meta/src/backup_restore/meta_snapshot_builder_v2.rs +++ b/src/meta/src/backup_restore/meta_snapshot_builder_v2.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![expect(dead_code, reason = "WIP")] - use std::future::Future; use itertools::Itertools; @@ -23,12 +21,35 @@ use risingwave_backup::MetaSnapshotId; use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta}; use risingwave_meta_model_v2 as model_v2; use risingwave_pb::hummock::PbHummockVersionDelta; -use sea_orm::{EntityTrait, QueryOrder, TransactionTrait}; +use sea_orm::{DbErr, EntityTrait, QueryOrder, TransactionTrait}; use crate::controller::SqlMetaStore; const VERSION: u32 = 2; +fn map_db_err(e: DbErr) -> BackupError { + BackupError::MetaStorage(e.into()) +} + +macro_rules! define_set_metadata { + ($( {$name:ident, $mod_path:ident::$mod_name:ident} ),*) => { + async fn set_metadata( + metadata: &mut MetadataV2, + txn: &sea_orm::DatabaseTransaction, + ) -> BackupResult<()> { + $( + metadata.$name = $mod_path::$mod_name::Entity::find() + .all(txn) + .await + .map_err(map_db_err)?; + )* + Ok(()) + } + }; +} + +risingwave_backup::for_all_metadata_models_v2!(define_set_metadata); + pub struct MetaSnapshotV2Builder { snapshot: MetaSnapshotV2, meta_store: SqlMetaStore, @@ -61,12 +82,12 @@ impl MetaSnapshotV2Builder { Some(sea_orm::AccessMode::ReadOnly), ) .await - .map_err(|e| BackupError::MetaStorage(e.into()))?; + .map_err(map_db_err)?; let version_deltas = model_v2::prelude::HummockVersionDelta::find() .order_by_asc(model_v2::hummock_version_delta::Column::Id) .all(&txn) .await - .map_err(|e| BackupError::MetaStorage(e.into()))? + .map_err(map_db_err)? .into_iter() .map_into::() .map(|pb_delta| HummockVersionDelta::from_persisted_protobuf(&pb_delta)); @@ -89,30 +110,14 @@ impl MetaSnapshotV2Builder { } redo_state }; - let version_stats = model_v2::prelude::HummockVersionStats::find_by_id( - hummock_version.id as model_v2::HummockVersionId, - ) - .one(&txn) - .await - .map_err(|e| BackupError::MetaStorage(e.into()))? - .unwrap_or_else(|| panic!("version stats for version {} not found", hummock_version.id)); - let compaction_configs = model_v2::prelude::CompactionConfig::find() - .all(&txn) - .await - .map_err(|e| BackupError::MetaStorage(e.into()))?; - - // TODO: other metadata - let cluster_id = "TODO".to_string(); - - txn.commit() - .await - .map_err(|e| BackupError::MetaStorage(e.into()))?; - self.snapshot.metadata = MetadataV2 { - cluster_id, + let mut metadata = MetadataV2 { hummock_version, - version_stats, - compaction_configs, + ..Default::default() }; + set_metadata(&mut metadata, &txn).await?; + + txn.commit().await.map_err(map_db_err)?; + self.snapshot.metadata = metadata; Ok(()) } diff --git a/src/meta/src/backup_restore/restore.rs b/src/meta/src/backup_restore/restore.rs index dfc4f37234b37..6165ba331d857 100644 --- a/src/meta/src/backup_restore/restore.rs +++ b/src/meta/src/backup_restore/restore.rs @@ -41,6 +41,17 @@ pub struct RestoreOpts { /// Type of meta store to restore. #[clap(long, value_enum, default_value_t = MetaBackend::Etcd)] pub meta_store_type: MetaBackend, + #[clap(long, default_value_t = String::from(""))] + pub sql_endpoint: String, + /// Username of sql backend, required when meta backend set to MySQL or PostgreSQL. + #[clap(long, default_value = "")] + pub sql_username: String, + /// Password of sql backend, required when meta backend set to MySQL or PostgreSQL. + #[clap(long, default_value = "")] + pub sql_password: String, + /// Database of sql backend, required when meta backend set to MySQL or PostgreSQL. + #[clap(long, default_value = "")] + pub sql_database: String, /// Etcd endpoints. #[clap(long, default_value_t = String::from(""))] pub etcd_endpoints: String, @@ -80,7 +91,7 @@ async fn restore_hummock_version( hummock_storage_url, Arc::new(ObjectStoreMetrics::unused()), "Version Checkpoint", - ObjectStoreConfig::default(), + Arc::new(ObjectStoreConfig::default()), ) .await, ); @@ -140,7 +151,7 @@ async fn restore_impl( match &meta_store { MetaStoreBackendImpl::Sql(m) => { if format_version < 2 { - todo!("write model V1 to meta store V2"); + unimplemented!("not supported: write model V1 to meta store V2"); } else { dispatch( target_id, @@ -227,6 +238,10 @@ mod tests { RestoreOpts { meta_snapshot_id: 1, meta_store_type: MetaBackend::Mem, + sql_endpoint: "".to_string(), + sql_username: "".to_string(), + sql_password: "".to_string(), + sql_database: "".to_string(), etcd_endpoints: "".to_string(), etcd_auth: false, etcd_username: "".to_string(), @@ -243,6 +258,7 @@ mod tests { SystemParams { state_store: Some("state_store".into()), data_directory: Some("data_directory".into()), + use_new_object_prefix_strategy: Some(true), backup_storage_url: Some("backup_storage_url".into()), backup_storage_directory: Some("backup_storage_directory".into()), ..SystemConfig::default().into_init_system_params() @@ -263,9 +279,10 @@ mod tests { let snapshot = MetaSnapshot { id: opts.meta_snapshot_id, metadata: ClusterMetadata { - hummock_version: HummockVersion { - id: 123, - ..Default::default() + hummock_version: { + let mut version = HummockVersion::default(); + version.id = 123; + version }, system_param: system_param.clone(), ..Default::default() @@ -445,9 +462,10 @@ mod tests { memcomparable::to_vec(&"some_value_2".to_string()).unwrap(), ), ]), - hummock_version: HummockVersion { - id: 123, - ..Default::default() + hummock_version: { + let mut version = HummockVersion::default(); + version.id = 123; + version }, system_param: system_param.clone(), ..Default::default() diff --git a/src/meta/src/backup_restore/restore_impl/v2.rs b/src/meta/src/backup_restore/restore_impl/v2.rs index 5195845f9e252..a431b455063bb 100644 --- a/src/meta/src/backup_restore/restore_impl/v2.rs +++ b/src/meta/src/backup_restore/restore_impl/v2.rs @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::iter; - use risingwave_backup::error::{BackupError, BackupResult}; use risingwave_backup::meta_snapshot::MetaSnapshot; use risingwave_backup::meta_snapshot_v2::{MetaSnapshotV2, MetadataV2}; use risingwave_backup::storage::{MetaSnapshotStorage, MetaSnapshotStorageRef}; use risingwave_backup::MetaSnapshotId; +use sea_orm::{DatabaseBackend, DbBackend, DbErr, Statement}; use crate::backup_restore::restore_impl::{Loader, Writer}; use crate::controller::SqlMetaStore; @@ -36,13 +35,51 @@ impl LoaderV2 { #[async_trait::async_trait] impl Loader for LoaderV2 { async fn load(&self, target_id: MetaSnapshotId) -> BackupResult> { - let target_snapshot: MetaSnapshotV2 = self.backup_store.get(target_id).await?; + let snapshot_list = &self.backup_store.manifest().snapshot_metadata; + let mut target_snapshot: MetaSnapshotV2 = self.backup_store.get(target_id).await?; tracing::info!( "snapshot {} before rewrite:\n{}", target_id, target_snapshot ); - todo!("validate and rewrite seq") + let newest_id = snapshot_list + .iter() + .map(|m| m.id) + .max() + .expect("should exist"); + assert!( + newest_id >= target_id, + "newest_id={}, target_id={}", + newest_id, + target_id + ); + + // validate and rewrite seq + if newest_id > target_id { + let newest_snapshot: MetaSnapshotV2 = self.backup_store.get(newest_id).await?; + for seq in &target_snapshot.metadata.hummock_sequences { + let newest = newest_snapshot + .metadata + .hummock_sequences + .iter() + .find(|s| s.name == seq.name) + .unwrap_or_else(|| { + panic!( + "violate superset requirement. Hummock sequence name {}", + seq.name + ) + }); + assert!(newest.seq >= seq.seq, "violate monotonicity requirement"); + } + target_snapshot.metadata.hummock_sequences = newest_snapshot.metadata.hummock_sequences; + tracing::info!( + "snapshot {} after rewrite by snapshot {}:\n{}", + target_id, + newest_id, + target_snapshot, + ); + } + Ok(target_snapshot) } } @@ -61,10 +98,100 @@ impl Writer for WriterModelV2ToMetaStoreV2 { async fn write(&self, target_snapshot: MetaSnapshot) -> BackupResult<()> { let metadata = target_snapshot.metadata; let db = &self.meta_store.conn; - insert_models(iter::once(metadata.version_stats), db).await?; - insert_models(metadata.compaction_configs, db).await?; - todo!("write other metadata") + insert_models(metadata.seaql_migrations.clone(), db).await?; + insert_models(metadata.clusters.clone(), db).await?; + insert_models(metadata.version_stats.clone(), db).await?; + insert_models(metadata.compaction_configs.clone(), db).await?; + insert_models(metadata.hummock_sequences.clone(), db).await?; + insert_models(metadata.workers.clone(), db).await?; + insert_models(metadata.worker_properties.clone(), db).await?; + insert_models(metadata.users.clone(), db).await?; + insert_models(metadata.user_privileges.clone(), db).await?; + insert_models(metadata.objects.clone(), db).await?; + insert_models(metadata.object_dependencies.clone(), db).await?; + insert_models(metadata.databases.clone(), db).await?; + insert_models(metadata.schemas.clone(), db).await?; + insert_models(metadata.streaming_jobs.clone(), db).await?; + insert_models(metadata.fragments.clone(), db).await?; + insert_models(metadata.actors.clone(), db).await?; + insert_models(metadata.actor_dispatchers.clone(), db).await?; + insert_models(metadata.connections.clone(), db).await?; + insert_models(metadata.sources.clone(), db).await?; + insert_models(metadata.tables.clone(), db).await?; + insert_models(metadata.sinks.clone(), db).await?; + insert_models(metadata.views.clone(), db).await?; + insert_models(metadata.indexes.clone(), db).await?; + insert_models(metadata.functions.clone(), db).await?; + insert_models(metadata.system_parameters.clone(), db).await?; + insert_models(metadata.catalog_versions.clone(), db).await?; + insert_models(metadata.subscriptions.clone(), db).await?; + insert_models(metadata.session_parameters.clone(), db).await?; + insert_models(metadata.secrets.clone(), db).await?; + // update_auto_inc must be called last. + update_auto_inc(&metadata, db).await?; + Ok(()) + } +} + +fn map_db_err(e: DbErr) -> BackupError { + BackupError::MetaStorage(e.into()) +} + +// TODO: the code snippet is similar to the one found in migration.rs +async fn update_auto_inc( + metadata: &MetadataV2, + db: &impl sea_orm::ConnectionTrait, +) -> BackupResult<()> { + match db.get_database_backend() { + DbBackend::MySql => { + if let Some(next_worker_id) = metadata.workers.iter().map(|w| w.worker_id + 1).max() { + db.execute(Statement::from_string( + DatabaseBackend::MySql, + format!("ALTER TABLE worker AUTO_INCREMENT = {next_worker_id};"), + )) + .await + .map_err(map_db_err)?; + } + if let Some(next_object_id) = metadata.objects.iter().map(|o| o.oid + 1).max() { + db.execute(Statement::from_string( + DatabaseBackend::MySql, + format!("ALTER TABLE object AUTO_INCREMENT = {next_object_id};"), + )) + .await + .map_err(map_db_err)?; + } + if let Some(next_user_id) = metadata.users.iter().map(|u| u.user_id + 1).max() { + db.execute(Statement::from_string( + DatabaseBackend::MySql, + format!("ALTER TABLE user AUTO_INCREMENT = {next_user_id};"), + )) + .await + .map_err(map_db_err)?; + } + } + DbBackend::Postgres => { + db.execute(Statement::from_string( + DatabaseBackend::Postgres, + "SELECT setval('worker_worker_id_seq', (SELECT MAX(worker_id) FROM worker));", + )) + .await + .map_err(map_db_err)?; + db.execute(Statement::from_string( + DatabaseBackend::Postgres, + "SELECT setval('object_oid_seq', (SELECT MAX(oid) FROM object) + 1);", + )) + .await + .map_err(map_db_err)?; + db.execute(Statement::from_string( + DatabaseBackend::Postgres, + "SELECT setval('user_user_id_seq', (SELECT MAX(user_id) FROM \"user\") + 1);", + )) + .await + .map_err(map_db_err)?; + } + DbBackend::Sqlite => {} } + Ok(()) } async fn insert_models( @@ -81,16 +208,13 @@ where if ::Entity::find() .one(db) .await - .map_err(|e| BackupError::MetaStorage(e.into()))? + .map_err(map_db_err)? .is_some() { return Err(BackupError::NonemptyMetaStorage); } for m in models { - m.into_active_model() - .insert(db) - .await - .map_err(|e| BackupError::MetaStorage(e.into()))?; + m.into_active_model().insert(db).await.map_err(map_db_err)?; } Ok(()) } diff --git a/src/meta/src/backup_restore/utils.rs b/src/meta/src/backup_restore/utils.rs index 48eec28f4ffea..6f6c1dd09ece0 100644 --- a/src/meta/src/backup_restore/utils.rs +++ b/src/meta/src/backup_restore/utils.rs @@ -17,11 +17,12 @@ use std::time::Duration; use anyhow::Context; use etcd_client::ConnectOptions; -use risingwave_backup::error::BackupResult; +use risingwave_backup::error::{BackupError, BackupResult}; use risingwave_backup::storage::{MetaSnapshotStorageRef, ObjectStoreMetaSnapshotStorage}; use risingwave_common::config::{MetaBackend, ObjectStoreConfig}; use risingwave_object_store::object::build_remote_object_store; use risingwave_object_store::object::object_metrics::ObjectStoreMetrics; +use sea_orm::DbBackend; use crate::backup_restore::RestoreOpts; use crate::controller::SqlMetaStore; @@ -32,7 +33,6 @@ use crate::MetaStoreBackend; pub enum MetaStoreBackendImpl { Etcd(EtcdMetaStore), Mem(MemStore), - #[expect(dead_code, reason = "WIP")] Sql(SqlMetaStore), } @@ -62,7 +62,24 @@ pub async fn get_meta_store(opts: RestoreOpts) -> BackupResult MetaStoreBackend::Mem, - MetaBackend::Sql => panic!("not supported"), + MetaBackend::Sql => MetaStoreBackend::Sql { + endpoint: opts.sql_endpoint, + }, + MetaBackend::Sqlite => MetaStoreBackend::Sql { + endpoint: format!("sqlite://{}?mode=rwc", opts.sql_endpoint), + }, + MetaBackend::Postgres => MetaStoreBackend::Sql { + endpoint: format!( + "postgres://{}:{}@{}/{}", + opts.sql_username, opts.sql_password, opts.sql_endpoint, opts.sql_database + ), + }, + MetaBackend::Mysql => MetaStoreBackend::Sql { + endpoint: format!( + "mysql://{}:{}@{}/{}", + opts.sql_username, opts.sql_password, opts.sql_endpoint, opts.sql_database + ), + }, }; match meta_store_backend { MetaStoreBackend::Etcd { @@ -80,7 +97,25 @@ pub async fn get_meta_store(opts: RestoreOpts) -> BackupResult Ok(MetaStoreBackendImpl::Mem(MemStore::new())), - MetaStoreBackend::Sql { .. } => panic!("not supported"), + MetaStoreBackend::Sql { endpoint } => { + let max_connection = if DbBackend::Sqlite.is_prefix_of(&endpoint) { + // Due to the fact that Sqlite is prone to the error "(code: 5) database is locked" under concurrent access, + // here we forcibly specify the number of connections as 1. + 1 + } else { + 10 + }; + let mut options = sea_orm::ConnectOptions::new(endpoint); + options + .max_connections(max_connection) + .connect_timeout(Duration::from_secs(10)) + .idle_timeout(Duration::from_secs(30)); + let conn = sea_orm::Database::connect(options) + .await + .map_err(|e| BackupError::MetaStorage(e.into()))?; + let meta_store_sql = SqlMetaStore::new(conn); + Ok(MetaStoreBackendImpl::Sql(meta_store_sql)) + } } } @@ -89,7 +124,7 @@ pub async fn get_backup_store(opts: RestoreOpts) -> BackupResult, } +/// Replacing an old table with a new one. All actors in the table job will be rebuilt. +/// Used for `ALTER TABLE` ([`Command::ReplaceTable`]) and sink into table ([`Command::CreateStreamingJob`]). #[derive(Debug, Clone)] pub struct ReplaceTablePlan { pub old_table_fragments: TableFragments, pub new_table_fragments: TableFragments, pub merge_updates: Vec, pub dispatchers: HashMap>, + /// For a table with connector, the `SourceExecutor` actor will also be rebuilt with new actor ids. + /// We need to reassign splits for it. + /// + /// Note that there's no `SourceBackfillExecutor` involved for table with connector, so we don't need to worry about + /// `backfill_splits`. pub init_split_assignment: SplitAssignment, } impl ReplaceTablePlan { fn actor_changes(&self) -> CommandActorChanges { - let worker_actors = self.new_table_fragments.worker_actor_ids(); - let barrier_inject_actors: &HashSet<_> = &self - .new_table_fragments - .barrier_inject_actor_ids() - .into_iter() - .collect(); - let to_add = worker_actors - .into_iter() - .flat_map(|(node_id, actors)| { - actors.into_iter().map(move |actor_id| ActorDesc { - id: actor_id, - node_id, - is_injectable: barrier_inject_actors.contains(&actor_id), - }) - }) - .collect_vec(); - CommandActorChanges { - to_add, - to_remove: self.old_table_fragments.actor_ids().into_iter().collect(), + let mut fragment_changes = HashMap::new(); + for fragment in self.new_table_fragments.fragments.values() { + let fragment_change = CommandFragmentChanges::NewFragment { + new_actors: fragment + .actors + .iter() + .map(|actor| { + ( + actor.actor_id, + self.new_table_fragments + .actor_status + .get(&actor.actor_id) + .expect("should exist") + .get_parallel_unit() + .expect("should set") + .worker_node_id, + ) + }) + .collect(), + table_ids: fragment + .state_table_ids + .iter() + .map(|table_id| TableId::new(*table_id)) + .collect(), + is_injectable: TableFragments::is_injectable(fragment.fragment_type_mask), + }; + assert!(fragment_changes + .insert(fragment.fragment_id, fragment_change) + .is_none()); + } + for fragment in self.old_table_fragments.fragments.values() { + assert!(fragment_changes + .insert(fragment.fragment_id, CommandFragmentChanges::RemoveFragment) + .is_none()); } + CommandActorChanges { fragment_changes } } } @@ -144,8 +169,8 @@ pub enum Command { /// drop actors, and then delete the table fragments info from meta store. DropStreamingJobs { actors: Vec, - unregistered_table_fragment_ids: HashSet, unregistered_state_table_ids: HashSet, + unregistered_fragment_ids: HashSet, }, /// `CreateStreamingJob` command generates a `Add` barrier by given info. @@ -166,6 +191,7 @@ pub enum Command { definition: String, ddl_type: DdlType, create_type: CreateType, + /// This is for create SINK into table. replace_table: Option, }, /// `CancelStreamingJob` command generates a `Stop` barrier including the actors of the given @@ -201,6 +227,22 @@ pub enum Command { /// `Throttle` command generates a `Throttle` barrier with the given throttle config to change /// the `rate_limit` of `FlowControl` Executor after `StreamScan` or Source. Throttle(ThrottleConfig), + + /// `CreateSubscription` command generates a `CreateSubscriptionMutation` to notify + /// materialize executor to start storing old value for subscription. + CreateSubscription { + subscription_id: u32, + upstream_mv_table_id: TableId, + retention_second: u64, + }, + + /// `DropSubscription` command generates a `DropSubscriptionMutation` to notify + /// materialize executor to stop storing old value when there is no + /// subscription depending on it. + DropSubscription { + subscription_id: u32, + upstream_mv_table_id: TableId, + }, } impl Command { @@ -221,70 +263,96 @@ impl Command { Command::Plain(_) => None, Command::Pause(_) => None, Command::Resume(_) => None, - Command::DropStreamingJobs { actors, .. } => Some(CommandActorChanges { - to_add: Default::default(), - to_remove: actors.iter().cloned().collect(), + Command::DropStreamingJobs { + unregistered_fragment_ids, + .. + } => Some(CommandActorChanges { + fragment_changes: unregistered_fragment_ids + .iter() + .map(|fragment_id| (*fragment_id, CommandFragmentChanges::RemoveFragment)) + .collect(), }), Command::CreateStreamingJob { table_fragments, replace_table, .. } => { - let worker_actors = table_fragments.worker_actor_ids(); - let barrier_inject_actors: &HashSet<_> = &table_fragments - .barrier_inject_actor_ids() - .into_iter() - .collect(); - let mut to_add = worker_actors - .into_iter() - .flat_map(|(node_id, actors)| { - actors.into_iter().map(move |actor_id| ActorDesc { - id: actor_id, - node_id, - is_injectable: barrier_inject_actors.contains(&actor_id), - }) + let fragment_changes = table_fragments + .fragments + .values() + .map(|fragment| { + ( + fragment.fragment_id, + CommandFragmentChanges::NewFragment { + new_actors: fragment + .actors + .iter() + .map(|actor| { + ( + actor.actor_id, + table_fragments + .actor_status + .get(&actor.actor_id) + .expect("should exist") + .get_parallel_unit() + .expect("should set") + .worker_node_id, + ) + }) + .collect(), + table_ids: fragment + .state_table_ids + .iter() + .map(|table_id| TableId::new(*table_id)) + .collect(), + is_injectable: TableFragments::is_injectable( + fragment.fragment_type_mask, + ), + }, + ) }) - .collect_vec(); + .collect(); + let mut changes = CommandActorChanges { fragment_changes }; if let Some(plan) = replace_table { - let CommandActorChanges { - to_add: to_add_plan, - to_remove, - } = plan.actor_changes(); - to_add.extend(to_add_plan); - Some(CommandActorChanges { to_add, to_remove }) - } else { - Some(CommandActorChanges { - to_add, - to_remove: Default::default(), - }) + let extra_change = plan.actor_changes(); + changes.extend(extra_change); } + + Some(changes) } Command::CancelStreamingJob(table_fragments) => Some(CommandActorChanges { - to_add: Default::default(), - to_remove: table_fragments.actor_ids().into_iter().collect(), + fragment_changes: table_fragments + .fragments + .values() + .map(|fragment| (fragment.fragment_id, CommandFragmentChanges::RemoveFragment)) + .collect(), + }), + Command::RescheduleFragment { reschedules, .. } => Some(CommandActorChanges { + fragment_changes: reschedules + .iter() + .map(|(fragment_id, reschedule)| { + ( + *fragment_id, + CommandFragmentChanges::Reschedule { + new_actors: reschedule + .added_actors + .iter() + .flat_map(|(node_id, actors)| { + actors.iter().map(|actor_id| (*actor_id, *node_id)) + }) + .collect(), + to_remove: reschedule.removed_actors.iter().cloned().collect(), + }, + ) + }) + .collect(), }), - Command::RescheduleFragment { reschedules, .. } => { - let mut to_add = vec![]; - let mut to_remove = HashSet::new(); - for reschedule in reschedules.values() { - for (node_id, added_actors) in &reschedule.added_actors { - for actor_id in added_actors { - to_add.push(ActorDesc { - id: *actor_id, - node_id: *node_id, - is_injectable: reschedule.injectable, - }); - } - } - to_remove.extend(reschedule.removed_actors.iter().copied()); - } - - Some(CommandActorChanges { to_add, to_remove }) - } Command::ReplaceTable(plan) => Some(plan.actor_changes()), Command::SourceSplitAssignment(_) => None, Command::Throttle(_) => None, + Command::CreateSubscription { .. } => None, + Command::DropSubscription { .. } => None, } } @@ -360,7 +428,7 @@ pub struct CommandContext { /// Differs from [`TracedEpoch`], this span focuses on the lifetime of the corresponding /// barrier, including the process of waiting for the barrier to be sent, flowing through the /// stream graph on compute nodes, and finishing its `post_collect` stuffs. - pub span: tracing::Span, + pub _span: tracing::Span, } impl CommandContext { @@ -383,7 +451,7 @@ impl CommandContext { command, kind, barrier_manager_context, - span, + _span: span, } } @@ -663,6 +731,22 @@ impl CommandContext { tracing::debug!("update mutation: {mutation:?}"); Some(mutation) } + + Command::CreateSubscription { + upstream_mv_table_id, + subscription_id, + .. + } => Some(Mutation::CreateSubscription(CreateSubscriptionMutation { + upstream_mv_table_id: upstream_mv_table_id.table_id, + subscription_id: *subscription_id, + })), + Command::DropSubscription { + upstream_mv_table_id, + subscription_id, + } => Some(Mutation::DropSubscription(DropSubscriptionMutation { + upstream_mv_table_id: upstream_mv_table_id.table_id, + subscription_id: *subscription_id, + })), }; mutation @@ -829,13 +913,9 @@ impl CommandContext { // Tell compute nodes to drop actors. self.clean_up(actors.clone()).await?; - let unregistered_state_table_ids = unregistered_state_table_ids - .iter() - .map(|table_id| table_id.table_id) - .collect_vec(); self.barrier_manager_context .hummock_manager - .unregister_table_ids(&unregistered_state_table_ids) + .unregister_table_ids(unregistered_state_table_ids.iter().cloned()) .await?; } @@ -852,12 +932,9 @@ impl CommandContext { // It won't clean the tables on failure, // since the failure could be recoverable. // As such it needs to be handled here. - let table_id = table_fragments.table_id().table_id; - let mut table_ids = table_fragments.internal_table_ids(); - table_ids.push(table_id); self.barrier_manager_context .hummock_manager - .unregister_table_ids(&table_ids) + .unregister_table_ids(table_fragments.all_table_ids().map(TableId::new)) .await?; match &self.barrier_manager_context.metadata_manager { @@ -867,7 +944,7 @@ impl CommandContext { // The logic is the same as above, for hummock_manager.unregister_table_ids. if let Err(e) = mgr .catalog_manager - .cancel_create_table_procedure( + .cancel_create_materialized_view_procedure( table_fragments.table_id().table_id, table_fragments.internal_table_ids(), ) @@ -898,7 +975,10 @@ impl CommandContext { } MetadataManager::V2(mgr) => { mgr.catalog_controller - .try_abort_creating_streaming_job(table_id as _, true) + .try_abort_creating_streaming_job( + table_fragments.table_id().table_id as _, + true, + ) .await?; } } @@ -1071,8 +1151,34 @@ impl CommandContext { ) .await; } + + Command::CreateSubscription { + subscription_id, .. + } => match &self.barrier_manager_context.metadata_manager { + MetadataManager::V1(mgr) => { + mgr.catalog_manager + .finish_create_subscription_procedure(*subscription_id) + .await?; + } + MetadataManager::V2(mgr) => { + mgr.catalog_controller + .finish_create_subscription_catalog(*subscription_id) + .await?; + } + }, + Command::DropSubscription { .. } => {} } Ok(()) } + + pub fn get_truncate_epoch(&self, retention_second: u64) -> Epoch { + let Some(truncate_timestamptz) = Timestamptz::from_secs( + self.prev_epoch.value().as_timestamptz().timestamp() - retention_second as i64, + ) else { + warn!(retention_second, prev_epoch = ?self.prev_epoch.value(), "invalid retention second value"); + return self.prev_epoch.value(); + }; + Epoch::from_unix_millis(truncate_timestamptz.timestamp_millis() as u64) + } } diff --git a/src/meta/src/barrier/info.rs b/src/meta/src/barrier/info.rs index 2c887eae02faa..f6617b9ceef47 100644 --- a/src/meta/src/barrier/info.rs +++ b/src/meta/src/barrier/info.rs @@ -14,23 +14,42 @@ use std::collections::{HashMap, HashSet}; +use risingwave_common::catalog::TableId; use risingwave_pb::common::PbWorkerNode; use tracing::warn; -use crate::manager::{ActiveStreamingWorkerNodes, ActorInfos, WorkerId}; -use crate::model::ActorId; +use crate::barrier::Command; +use crate::manager::{ActiveStreamingWorkerNodes, ActorInfos, InflightFragmentInfo, WorkerId}; +use crate::model::{ActorId, FragmentId}; #[derive(Debug, Clone)] -pub struct ActorDesc { - pub id: ActorId, - pub node_id: WorkerId, - pub is_injectable: bool, +pub(crate) enum CommandFragmentChanges { + NewFragment { + new_actors: HashMap, + table_ids: HashSet, + is_injectable: bool, + }, + Reschedule { + new_actors: HashMap, + to_remove: HashSet, + }, + RemoveFragment, } #[derive(Debug, Clone)] pub struct CommandActorChanges { - pub(crate) to_add: Vec, - pub(crate) to_remove: HashSet, + pub(crate) fragment_changes: HashMap, +} + +impl CommandActorChanges { + pub fn extend(&mut self, other: CommandActorChanges) { + for (fragment_id, fragment) in other.fragment_changes { + assert!(self + .fragment_changes + .insert(fragment_id, fragment) + .is_none()); + } + } } /// [`InflightActorInfo`] resolves the actor info read from meta store for @@ -48,35 +67,64 @@ pub struct InflightActorInfo { /// `actor_id` => `WorkerId` pub actor_location_map: HashMap, + + /// `mv_table_id` => `subscription_id` => retention seconds + pub mv_depended_subscriptions: HashMap>, + + pub fragment_infos: HashMap, } impl InflightActorInfo { /// Resolve inflight actor info from given nodes and actors that are loaded from meta store. It will be used during recovery to rebuild all streaming actors. - pub fn resolve(active_nodes: &ActiveStreamingWorkerNodes, actor_infos: ActorInfos) -> Self { + pub fn resolve( + active_nodes: &ActiveStreamingWorkerNodes, + actor_infos: ActorInfos, + mv_depended_subscriptions: HashMap>, + ) -> Self { let node_map = active_nodes.current().clone(); - let actor_map = actor_infos - .actor_maps - .into_iter() - .map(|(node_id, actor_ids)| (node_id, actor_ids.into_iter().collect::>())) - .collect::>(); + let actor_map = { + let mut map: HashMap<_, HashSet<_>> = HashMap::new(); + for info in actor_infos.fragment_infos.values() { + for (actor_id, worker_id) in &info.actors { + map.entry(*worker_id).or_default().insert(*actor_id); + } + } + map + }; - let actor_map_to_send = actor_infos - .barrier_inject_actor_maps - .into_iter() - .map(|(node_id, actor_ids)| (node_id, actor_ids.into_iter().collect::>())) - .collect::>(); + let actor_map_to_send = { + let mut map: HashMap<_, HashSet<_>> = HashMap::new(); + for info in actor_infos + .fragment_infos + .values() + .filter(|info| info.is_injectable) + { + for (actor_id, worker_id) in &info.actors { + map.entry(*worker_id).or_default().insert(*actor_id); + } + } + map + }; - let actor_location_map = actor_map - .iter() - .flat_map(|(node_id, actor_ids)| actor_ids.iter().map(|actor_id| (*actor_id, *node_id))) - .collect::>(); + let actor_location_map = actor_infos + .fragment_infos + .values() + .flat_map(|fragment| { + fragment + .actors + .iter() + .map(|(actor_id, workder_id)| (*actor_id, *workder_id)) + }) + .collect(); Self { node_map, actor_map, actor_map_to_send, actor_location_map, + mv_depended_subscriptions, + fragment_infos: actor_infos.fragment_infos, } } @@ -96,41 +144,117 @@ impl InflightActorInfo { /// Apply some actor changes before issuing a barrier command, if the command contains any new added actors, we should update /// the info correspondingly. - pub fn pre_apply(&mut self, changes: Option) { - if let Some(CommandActorChanges { to_add, .. }) = changes { - for actor_desc in to_add { - assert!(self.node_map.contains_key(&actor_desc.node_id)); + pub fn pre_apply(&mut self, command: &Command) { + if let Some(CommandActorChanges { fragment_changes }) = command.actor_changes() { + let mut to_add = HashMap::new(); + for (fragment_id, change) in fragment_changes { + match change { + CommandFragmentChanges::NewFragment { + new_actors, + table_ids, + is_injectable, + } => { + for (actor_id, node_id) in &new_actors { + assert!(to_add + .insert(*actor_id, (*node_id, is_injectable)) + .is_none()); + } + assert!(self + .fragment_infos + .insert( + fragment_id, + InflightFragmentInfo { + actors: new_actors, + state_table_ids: table_ids, + is_injectable, + } + ) + .is_none()); + } + CommandFragmentChanges::Reschedule { new_actors, .. } => { + let info = self + .fragment_infos + .get_mut(&fragment_id) + .expect("should exist"); + let actors = &mut info.actors; + for (actor_id, node_id) in new_actors { + assert!(to_add + .insert(actor_id, (node_id, info.is_injectable)) + .is_none()); + assert!(actors.insert(actor_id, node_id).is_none()); + } + } + CommandFragmentChanges::RemoveFragment => {} + } + } + for (actor_id, (node_id, is_injectable)) in to_add { + assert!(self.node_map.contains_key(&node_id)); assert!( - self.actor_map - .entry(actor_desc.node_id) - .or_default() - .insert(actor_desc.id), + self.actor_map.entry(node_id).or_default().insert(actor_id), "duplicate actor in command changes" ); - if actor_desc.is_injectable { + if is_injectable { assert!( self.actor_map_to_send - .entry(actor_desc.node_id) + .entry(node_id) .or_default() - .insert(actor_desc.id), + .insert(actor_id), "duplicate actor in command changes" ); } assert!( - self.actor_location_map - .insert(actor_desc.id, actor_desc.node_id) - .is_none(), + self.actor_location_map.insert(actor_id, node_id).is_none(), "duplicate actor in command changes" ); } - }; + } + if let Command::CreateSubscription { + subscription_id, + upstream_mv_table_id, + retention_second, + } = command + { + if let Some(prev_retiontion) = self + .mv_depended_subscriptions + .entry(*upstream_mv_table_id) + .or_default() + .insert(*subscription_id, *retention_second) + { + warn!(subscription_id, ?upstream_mv_table_id, mv_depended_subscriptions = ?self.mv_depended_subscriptions, prev_retiontion, "add an existing subscription id"); + } + } } /// Apply some actor changes after the barrier command is collected, if the command contains any actors that are dropped, we should /// remove that from the snapshot correspondingly. - pub fn post_apply(&mut self, changes: Option) { - if let Some(CommandActorChanges { to_remove, .. }) = changes { - for actor_id in to_remove { + pub fn post_apply(&mut self, command: &Command) { + if let Some(fragment_changes) = command.actor_changes() { + let mut all_to_remove = HashSet::new(); + for (fragment_id, changes) in fragment_changes.fragment_changes { + match changes { + CommandFragmentChanges::NewFragment { .. } => {} + CommandFragmentChanges::Reschedule { to_remove, .. } => { + let info = self + .fragment_infos + .get_mut(&fragment_id) + .expect("should exist"); + for actor_id in to_remove { + assert!(all_to_remove.insert(actor_id)); + assert!(info.actors.remove(&actor_id).is_some()); + } + } + CommandFragmentChanges::RemoveFragment => { + let info = self + .fragment_infos + .remove(&fragment_id) + .expect("should exist"); + for (actor_id, _) in info.actors { + assert!(all_to_remove.insert(actor_id)); + } + } + } + } + for actor_id in all_to_remove { let node_id = self .actor_location_map .remove(&actor_id) @@ -145,6 +269,25 @@ impl InflightActorInfo { self.actor_map_to_send .retain(|_, actor_ids| !actor_ids.is_empty()); } + if let Command::DropSubscription { + subscription_id, + upstream_mv_table_id, + } = command + { + let removed = match self.mv_depended_subscriptions.get_mut(upstream_mv_table_id) { + Some(subscriptions) => { + let removed = subscriptions.remove(subscription_id).is_some(); + if removed && subscriptions.is_empty() { + self.mv_depended_subscriptions.remove(upstream_mv_table_id); + } + removed + } + None => false, + }; + if !removed { + warn!(subscription_id, ?upstream_mv_table_id, mv_depended_subscriptions = ?self.mv_depended_subscriptions, "remove a non-existing subscription id"); + } + } } /// Returns actor list to collect in the target worker node. @@ -164,4 +307,11 @@ impl InflightActorInfo { .unwrap_or_default() .into_iter() } + + pub fn existing_table_ids(&self) -> HashSet { + self.fragment_infos + .values() + .flat_map(|info| info.state_table_ids.iter().cloned()) + .collect() + } } diff --git a/src/meta/src/barrier/mod.rs b/src/meta/src/barrier/mod.rs index 05dbdddfc3320..bb6737735dd44 100644 --- a/src/meta/src/barrier/mod.rs +++ b/src/meta/src/barrier/mod.rs @@ -30,6 +30,7 @@ use risingwave_common::catalog::TableId; use risingwave_common::system_param::reader::SystemParamsRead; use risingwave_common::system_param::PAUSE_ON_NEXT_BOOTSTRAP_KEY; use risingwave_common::util::epoch::{Epoch, INVALID_EPOCH}; +use risingwave_hummock_sdk::change_log::build_table_change_log_delta; use risingwave_hummock_sdk::table_watermark::{ merge_multiple_new_table_watermarks, TableWatermarks, }; @@ -425,6 +426,7 @@ enum CompletingCommand { // that has finished but not checkpointed. If there is any, we will force checkpoint on the next barrier join_handle: JoinHandle>, }, + #[expect(dead_code)] Err(MetaError), } @@ -898,10 +900,7 @@ impl GlobalBarrierManagerContext { BarrierKind::Initial => {} BarrierKind::Checkpoint(epochs) => { let commit_info = collect_commit_epoch_info(resps, command_ctx, epochs); - new_snapshot = self - .hummock_manager - .commit_epoch(command_ctx.prev_epoch.value().0, commit_info) - .await?; + new_snapshot = self.hummock_manager.commit_epoch(commit_info).await?; } BarrierKind::Barrier => { new_snapshot = Some(self.hummock_manager.update_current_epoch(prev_epoch)); @@ -1082,16 +1081,20 @@ impl GlobalBarrierManagerContext { &self, active_nodes: &ActiveStreamingWorkerNodes, ) -> MetaResult { + let subscriptions = self + .metadata_manager + .get_mv_depended_subscriptions() + .await?; let info = match &self.metadata_manager { MetadataManager::V1(mgr) => { let all_actor_infos = mgr.fragment_manager.load_all_actors().await; - InflightActorInfo::resolve(active_nodes, all_actor_infos) + InflightActorInfo::resolve(active_nodes, all_actor_infos, subscriptions) } MetadataManager::V2(mgr) => { let all_actor_infos = mgr.catalog_controller.load_all_actors().await?; - InflightActorInfo::resolve(active_nodes, all_actor_infos) + InflightActorInfo::resolve(active_nodes, all_actor_infos, subscriptions) } }; @@ -1144,11 +1147,12 @@ pub type BarrierManagerRef = GlobalBarrierManagerContext; fn collect_commit_epoch_info( resps: Vec, command_ctx: &CommandContext, - _epochs: &Vec, + epochs: &Vec, ) -> CommitEpochInfo { let mut sst_to_worker: HashMap = HashMap::new(); let mut synced_ssts: Vec = vec![]; let mut table_watermarks = Vec::with_capacity(resps.len()); + let mut old_value_ssts = Vec::with_capacity(resps.len()); for resp in resps { let ssts_iter = resp.synced_sstables.into_iter().map(|grouped| { let sst_info = grouped.sst.expect("field not None"); @@ -1161,6 +1165,7 @@ fn collect_commit_epoch_info( }); synced_ssts.extend(ssts_iter); table_watermarks.push(resp.table_watermarks); + old_value_ssts.extend(resp.old_value_sstables); } let new_table_fragment_info = if let Command::CreateStreamingJob { table_fragments, .. @@ -1179,6 +1184,26 @@ fn collect_commit_epoch_info( None }; + let table_new_change_log = build_table_change_log_delta( + old_value_ssts.into_iter(), + synced_ssts.iter().map(|sst| &sst.sst_info), + epochs, + command_ctx + .info + .mv_depended_subscriptions + .iter() + .filter_map(|(mv_table_id, subscriptions)| { + subscriptions.values().max().map(|max_retention| { + ( + mv_table_id.table_id, + command_ctx.get_truncate_epoch(*max_retention).0, + ) + }) + }), + ); + + let epoch = command_ctx.prev_epoch.value().0; + CommitEpochInfo::new( synced_ssts, merge_multiple_new_table_watermarks( @@ -1199,5 +1224,8 @@ fn collect_commit_epoch_info( ), sst_to_worker, new_table_fragment_info, + table_new_change_log, + BTreeMap::from_iter([(epoch, command_ctx.info.existing_table_ids())]), + epoch, ) } diff --git a/src/meta/src/barrier/recovery.rs b/src/meta/src/barrier/recovery.rs index a3f19e555bf75..3bb51b3b5ef73 100644 --- a/src/meta/src/barrier/recovery.rs +++ b/src/meta/src/barrier/recovery.rs @@ -67,6 +67,7 @@ impl GlobalBarrierManagerContext { async fn clean_dirty_streaming_jobs(&self) -> MetaResult<()> { match &self.metadata_manager { MetadataManager::V1(mgr) => { + mgr.catalog_manager.clean_dirty_subscription().await?; // Please look at `CatalogManager::clean_dirty_tables` for more details. mgr.catalog_manager .clean_dirty_tables(mgr.fragment_manager.clone()) @@ -97,6 +98,7 @@ impl GlobalBarrierManagerContext { .await; } MetadataManager::V2(mgr) => { + mgr.catalog_controller.clean_dirty_subscription().await?; let ReleaseContext { source_ids, .. } = mgr.catalog_controller.clean_dirty_creating_jobs().await?; @@ -110,24 +112,11 @@ impl GlobalBarrierManagerContext { Ok(()) } - async fn purge_state_table_from_hummock(&self) -> MetaResult<()> { - let all_state_table_ids = match &self.metadata_manager { - MetadataManager::V1(mgr) => mgr - .catalog_manager - .list_tables() - .await - .into_iter() - .map(|t| t.id) - .collect_vec(), - MetadataManager::V2(mgr) => mgr - .catalog_controller - .list_all_state_table_ids() - .await? - .into_iter() - .map(|id| id as u32) - .collect_vec(), - }; - self.hummock_manager.purge(&all_state_table_ids).await?; + async fn purge_state_table_from_hummock( + &self, + all_state_table_ids: &HashSet, + ) -> MetaResult<()> { + self.hummock_manager.purge(all_state_table_ids).await?; Ok(()) } @@ -159,7 +148,7 @@ impl GlobalBarrierManagerContext { if fragments.is_created() { // If the mview is already created, we don't need to recover it. mgr.catalog_manager - .finish_create_table_procedure(internal_tables, mview) + .finish_create_materialized_view_procedure(internal_tables, mview) .await?; tracing::debug!("notified frontend for stream job {}", table_id.table_id); } else { @@ -205,7 +194,7 @@ impl GlobalBarrierManagerContext { // and mark catalog as created and commit to meta. // both of these are done by catalog manager. catalog_manager - .finish_create_table_procedure(internal_tables, table.clone()) + .finish_create_materialized_view_procedure(internal_tables, table.clone()) .await?; tracing::debug!("notified frontend for stream job {}", table.id); }; @@ -283,7 +272,7 @@ impl GlobalBarrierManagerContext { tracing::debug!("recovering stream job {}", id); finished.await.ok().context("failed to finish command")??; tracing::debug!(id, "finished stream job"); - catalog_controller.finish_streaming_job(id).await?; + catalog_controller.finish_streaming_job(id, None).await?; }; if let Err(e) = &res { tracing::error!( @@ -311,30 +300,22 @@ impl GlobalBarrierManagerContext { let (dropped_actors, cancelled) = scheduled_barriers.pre_apply_drop_cancel_scheduled(); let applied = !dropped_actors.is_empty() || !cancelled.is_empty(); if !cancelled.is_empty() { - let unregister_table_ids = match &self.metadata_manager { + match &self.metadata_manager { MetadataManager::V1(mgr) => { mgr.fragment_manager .drop_table_fragments_vec(&cancelled) - .await? + .await?; } MetadataManager::V2(mgr) => { - let mut unregister_table_ids = Vec::new(); for job_id in cancelled { - let (_, table_ids_to_unregister) = mgr - .catalog_controller + mgr.catalog_controller .try_abort_creating_streaming_job(job_id.table_id as _, true) .await?; - unregister_table_ids.extend(table_ids_to_unregister); } - unregister_table_ids - .into_iter() - .map(|table_id| table_id as u32) - .collect() } }; - self.hummock_manager - .unregister_table_ids(&unregister_table_ids) - .await?; + // no need to unregister state table id from hummock manager here, because it's expected that + // we call `purge_state_table_from_hummock` anyway after the current method returns. } Ok(applied) } @@ -375,11 +356,6 @@ impl GlobalBarrierManager { .await .context("clean dirty streaming jobs")?; - self.context - .purge_state_table_from_hummock() - .await - .context("purge state table from hummock")?; - // Mview progress needs to be recovered. tracing::info!("recovering mview progress"); self.context @@ -434,18 +410,6 @@ impl GlobalBarrierManager { })? }; - let mut control_stream_manager = - ControlStreamManager::new(self.context.clone()); - - control_stream_manager - .reset(prev_epoch.value().0, active_streaming_nodes.current()) - .await - .inspect_err(|err| { - warn!(error = %err.as_report(), "reset compute nodes failed"); - })?; - - self.context.sink_manager.reset().await; - if self .context .pre_apply_drop_cancel(&self.scheduled_barriers) @@ -460,6 +424,25 @@ impl GlobalBarrierManager { })? } + let info = info; + + self.context + .purge_state_table_from_hummock(&info.existing_table_ids()) + .await + .context("purge state table from hummock")?; + + let mut control_stream_manager = + ControlStreamManager::new(self.context.clone()); + + control_stream_manager + .reset(prev_epoch.value().0, active_streaming_nodes.current()) + .await + .inspect_err(|err| { + warn!(error = %err.as_report(), "reset compute nodes failed"); + })?; + + self.context.sink_manager.reset().await; + // update and build all actors. self.context.update_actors(&info).await.inspect_err(|err| { warn!(error = %err.as_report(), "update actors failed"); @@ -1087,16 +1070,6 @@ impl GlobalBarrierManagerContext { new_plan.parallel_unit_plan.insert(*from, to); } - assert!( - new_plan - .parallel_unit_plan - .values() - .map(|pu| pu.id) - .all_unique(), - "target parallel units must be unique: {:?}", - new_plan.parallel_unit_plan - ); - new_plan.insert(self.env.meta_store().as_kv()).await?; Ok(new_plan) } diff --git a/src/meta/src/barrier/rpc.rs b/src/meta/src/barrier/rpc.rs index d23859609e05e..0a7e6d4e1e950 100644 --- a/src/meta/src/barrier/rpc.rs +++ b/src/meta/src/barrier/rpc.rs @@ -27,11 +27,12 @@ use itertools::Itertools; use risingwave_common::hash::ActorId; use risingwave_common::util::tracing::TracingContext; use risingwave_pb::common::{ActorInfo, WorkerNode}; -use risingwave_pb::stream_plan::{Barrier, BarrierMutation, StreamActor}; +use risingwave_pb::stream_plan::{Barrier, BarrierMutation}; use risingwave_pb::stream_service::{ streaming_control_stream_request, streaming_control_stream_response, BarrierCompleteResponse, - BroadcastActorInfoTableRequest, BuildActorsRequest, DropActorsRequest, InjectBarrierRequest, - StreamingControlStreamRequest, StreamingControlStreamResponse, UpdateActorsRequest, + BroadcastActorInfoTableRequest, BuildActorInfo, BuildActorsRequest, DropActorsRequest, + InjectBarrierRequest, StreamingControlStreamRequest, StreamingControlStreamResponse, + UpdateActorsRequest, }; use risingwave_rpc_client::error::RpcError; use risingwave_rpc_client::StreamClient; @@ -293,6 +294,12 @@ impl ControlStreamManager { barrier: Some(barrier), actor_ids_to_send, actor_ids_to_collect, + table_ids_to_sync: command_context + .info + .existing_table_ids() + .iter() + .map(|table_id| table_id.table_id) + .collect(), }, ), ), @@ -427,7 +434,7 @@ impl StreamRpcManager { worker_nodes: &HashMap, broadcast_worker_ids: impl Iterator, actor_infos_to_broadcast: impl Iterator, - node_actors_to_create: impl Iterator)>, + node_actors_to_create: impl Iterator)>, ) -> MetaResult<()> { let actor_infos = actor_infos_to_broadcast.collect_vec(); let mut node_actors_to_create = node_actors_to_create.collect::>(); @@ -446,7 +453,7 @@ impl StreamRpcManager { .await?; if let Some(actors) = actors { let request_id = Self::new_request_id(); - let actor_ids = actors.iter().map(|actor| actor.actor_id).collect_vec(); + let actor_ids = actors.iter().map(|actor| actor.actor.as_ref().unwrap().actor_id).collect_vec(); tracing::debug!(request_id = request_id.as_str(), actors = ?actor_ids, "update actors"); client .update_actors(UpdateActorsRequest { request_id, actors }) diff --git a/src/meta/src/barrier/state.rs b/src/meta/src/barrier/state.rs index f24bbb5637220..f3b49b8fa2709 100644 --- a/src/meta/src/barrier/state.rs +++ b/src/meta/src/barrier/state.rs @@ -76,10 +76,9 @@ impl BarrierManagerState { /// Returns the inflight actor infos that have included the newly added actors in the given command. The dropped actors /// will be removed from the state after the info get resolved. pub fn apply_command(&mut self, command: &Command) -> InflightActorInfo { - let changes = command.actor_changes(); - self.inflight_actor_infos.pre_apply(changes.clone()); + self.inflight_actor_infos.pre_apply(command); let info = self.inflight_actor_infos.clone(); - self.inflight_actor_infos.post_apply(changes); + self.inflight_actor_infos.post_apply(command); info } diff --git a/src/meta/src/controller/catalog.rs b/src/meta/src/controller/catalog.rs index 111e8e5ab9fe3..a49fc7a8a0690 100644 --- a/src/meta/src/controller/catalog.rs +++ b/src/meta/src/controller/catalog.rs @@ -18,10 +18,8 @@ use std::sync::Arc; use anyhow::anyhow; use itertools::Itertools; -use risingwave_common::catalog::{ - is_subscription_internal_table, TableOption, DEFAULT_SCHEMA_NAME, SYSTEM_SCHEMAS, -}; -use risingwave_common::util::stream_graph_visitor::visit_stream_node_cont; +use risingwave_common::catalog::{TableOption, DEFAULT_SCHEMA_NAME, SYSTEM_SCHEMAS}; +use risingwave_common::util::stream_graph_visitor::visit_stream_node_cont_mut; use risingwave_common::{bail, current_cluster_version}; use risingwave_connector::source::UPSTREAM_SOURCE_KEY; use risingwave_meta_model_v2::object::ObjectType; @@ -29,14 +27,16 @@ use risingwave_meta_model_v2::prelude::*; use risingwave_meta_model_v2::table::TableType; use risingwave_meta_model_v2::{ actor, connection, database, fragment, function, index, object, object_dependency, schema, - sink, source, streaming_job, subscription, table, user_privilege, view, ActorId, + secret, sink, source, streaming_job, subscription, table, user_privilege, view, ActorId, ActorUpstreamActors, ColumnCatalogArray, ConnectionId, CreateType, DatabaseId, FragmentId, FunctionId, I32Array, IndexId, JobStatus, ObjectId, PrivateLinkService, Property, SchemaId, - SinkId, SourceId, StreamNode, StreamSourceInfo, StreamingParallelism, TableId, UserId, + SecretId, SinkId, SourceId, StreamNode, StreamSourceInfo, StreamingParallelism, SubscriptionId, + TableId, UserId, ViewId, }; +use risingwave_pb::catalog::subscription::SubscriptionState; use risingwave_pb::catalog::table::PbTableType; use risingwave_pb::catalog::{ - PbComment, PbConnection, PbDatabase, PbFunction, PbIndex, PbSchema, PbSink, PbSource, + PbComment, PbConnection, PbDatabase, PbFunction, PbIndex, PbSchema, PbSecret, PbSink, PbSource, PbSubscription, PbTable, PbView, }; use risingwave_pb::meta::cancel_creating_jobs_request::PbCreatingJobInfo; @@ -45,7 +45,9 @@ use risingwave_pb::meta::relation::PbRelationInfo; use risingwave_pb::meta::subscribe_response::{ Info as NotificationInfo, Info, Operation as NotificationOperation, Operation, }; -use risingwave_pb::meta::{PbRelation, PbRelationGroup}; +use risingwave_pb::meta::{ + FragmentParallelUnitMapping, PbFragmentWorkerSlotMapping, PbRelation, PbRelationGroup, +}; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::FragmentTypeFlag; use risingwave_pb::user::PbUserInfo; @@ -58,12 +60,13 @@ use sea_orm::{ }; use tokio::sync::{RwLock, RwLockReadGuard}; +use super::utils::check_subscription_name_duplicate; use crate::controller::rename::{alter_relation_rename, alter_relation_rename_refs}; use crate::controller::utils::{ check_connection_name_duplicate, check_database_name_duplicate, check_function_signature_duplicate, check_relation_name_duplicate, check_schema_name_duplicate, - ensure_object_id, ensure_object_not_refer, ensure_schema_empty, ensure_user_id, - get_fragment_mappings, get_fragment_mappings_by_jobs, get_referring_objects, + check_secret_name_duplicate, ensure_object_id, ensure_object_not_refer, ensure_schema_empty, + ensure_user_id, get_fragment_mappings_by_jobs, get_referring_objects, get_referring_objects_cascade, get_user_privilege, list_user_info_by_ids, resolve_source_register_info_for_jobs, PartialObject, }; @@ -96,6 +99,8 @@ pub struct ReleaseContext { pub(crate) source_fragments: HashMap>, /// Dropped actors. pub(crate) removed_actors: HashSet, + + pub(crate) removed_fragments: HashSet, } impl CatalogController { @@ -224,7 +229,7 @@ impl CatalogController { .all(&txn) .await?; - let (source_fragments, removed_actors) = + let (source_fragments, removed_actors, removed_fragments) = resolve_source_register_info_for_jobs(&txn, streaming_jobs.clone()).await?; let state_table_ids: Vec = Table::find() @@ -274,7 +279,17 @@ impl CatalogController { .into_tuple() .all(&txn) .await?; - let fragment_mappings = get_fragment_mappings_by_jobs(&txn, streaming_jobs.clone()).await?; + + let fragment_mappings = get_fragment_mappings_by_jobs(&txn, streaming_jobs.clone()) + .await? + .into_iter() + .map( + |FragmentParallelUnitMapping { fragment_id, .. }| PbFragmentWorkerSlotMapping { + fragment_id, + mapping: None, + }, + ) + .collect(); // The schema and objects in the database will be delete cascade. let res = Object::delete_by_id(database_id).exec(&txn).await?; @@ -295,6 +310,7 @@ impl CatalogController { }), ) .await; + self.notify_fragment_mapping(NotificationOperation::Delete, fragment_mappings) .await; Ok(( @@ -305,6 +321,7 @@ impl CatalogController { connections, source_fragments, removed_actors, + removed_fragments, }, version, )) @@ -402,6 +419,117 @@ impl CatalogController { Ok(version) } + pub async fn create_subscription_catalog( + &self, + pb_subscription: &mut PbSubscription, + ) -> MetaResult<()> { + let inner = self.inner.write().await; + let txn = inner.db.begin().await?; + + ensure_user_id(pb_subscription.owner as _, &txn).await?; + ensure_object_id(ObjectType::Database, pb_subscription.database_id as _, &txn).await?; + ensure_object_id(ObjectType::Schema, pb_subscription.schema_id as _, &txn).await?; + check_subscription_name_duplicate(pb_subscription, &txn).await?; + + let obj = Self::create_object( + &txn, + ObjectType::Subscription, + pb_subscription.owner as _, + Some(pb_subscription.database_id as _), + Some(pb_subscription.schema_id as _), + ) + .await?; + pb_subscription.id = obj.oid as _; + let subscription: subscription::ActiveModel = pb_subscription.clone().into(); + Subscription::insert(subscription).exec(&txn).await?; + + // record object dependency. + ObjectDependency::insert(object_dependency::ActiveModel { + oid: Set(pb_subscription.dependent_table_id as _), + used_by: Set(pb_subscription.id as _), + ..Default::default() + }) + .exec(&txn) + .await?; + txn.commit().await?; + Ok(()) + } + + pub async fn finish_create_subscription_catalog(&self, subscription_id: u32) -> MetaResult<()> { + let inner = self.inner.write().await; + let txn = inner.db.begin().await?; + let job_id = subscription_id as i32; + + // update `created_at` as now() and `created_at_cluster_version` as current cluster version. + let res = Object::update_many() + .col_expr(object::Column::CreatedAt, Expr::current_timestamp().into()) + .col_expr( + object::Column::CreatedAtClusterVersion, + current_cluster_version().into(), + ) + .filter(object::Column::Oid.eq(job_id)) + .exec(&txn) + .await?; + if res.rows_affected == 0 { + return Err(MetaError::catalog_id_not_found("subscription", job_id)); + } + + // mark the target subscription as `Create`. + let job = subscription::ActiveModel { + subscription_id: Set(job_id), + subscription_state: Set(SubscriptionState::Created.into()), + ..Default::default() + }; + job.update(&txn).await?; + txn.commit().await?; + + Ok(()) + } + + pub async fn notify_create_subscription( + &self, + subscription_id: u32, + ) -> MetaResult { + let inner = self.inner.write().await; + let txn = inner.db.begin().await?; + let job_id = subscription_id as i32; + let (subscription, obj) = Subscription::find_by_id(job_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("subscription", job_id))?; + txn.commit().await?; + + let version = self + .notify_frontend( + Operation::Add, + Info::RelationGroup(PbRelationGroup { + relations: vec![PbRelation { + relation_info: PbRelationInfo::Subscription( + ObjectModel(subscription, obj.unwrap()).into(), + ) + .into(), + }], + }), + ) + .await; + Ok(version) + } + + pub async fn clean_dirty_subscription(&self) -> MetaResult<()> { + let inner = self.inner.write().await; + let txn = inner.db.begin().await?; + let _res = Subscription::delete_many() + .filter( + subscription::Column::SubscriptionState + .eq(Into::::into(SubscriptionState::Init)), + ) + .exec(&txn) + .await?; + txn.commit().await?; + Ok(()) + } + pub async fn list_background_creating_mviews(&self) -> MetaResult> { let inner = self.inner.read().await; let tables = Table::find() @@ -469,6 +597,29 @@ impl CatalogController { } })); + let subscription_dependencies: Vec<(SubscriptionId, TableId)> = Subscription::find() + .select_only() + .columns([ + subscription::Column::SubscriptionId, + subscription::Column::DependentTableId, + ]) + .join(JoinType::InnerJoin, subscription::Relation::Object.def()) + .filter( + subscription::Column::SubscriptionState + .eq(Into::::into(SubscriptionState::Created)) + .and(subscription::Column::DependentTableId.is_not_null()), + ) + .into_tuple() + .all(&inner.db) + .await?; + + obj_dependencies.extend(subscription_dependencies.into_iter().map( + |(subscription_id, table_id)| PbObjectDependencies { + object_id: subscription_id as _, + referenced_object_id: table_id as _, + }, + )); + Ok(obj_dependencies) } @@ -479,6 +630,7 @@ impl CatalogController { } /// `clean_dirty_creating_jobs` cleans up creating jobs that are creating in Foreground mode or in Initial status. + /// FIXME(kwannoel): Notify deleted objects to the frontend. pub async fn clean_dirty_creating_jobs(&self) -> MetaResult { let inner = self.inner.write().await; let txn = inner.db.begin().await?; @@ -741,7 +893,7 @@ impl CatalogController { let mut pb_stream_node = stream_node.to_protobuf(); - visit_stream_node_cont(&mut pb_stream_node, |node| { + visit_stream_node_cont_mut(&mut pb_stream_node, |node| { if let Some(NodeBody::Union(_)) = node.node_body { node.input.retain_mut(|input| { if let Some(NodeBody::Merge(merge_node)) = &mut input.node_body @@ -814,171 +966,6 @@ impl CatalogController { Ok(true) } - /// `finish_streaming_job` marks job related objects as `Created` and notify frontend. - pub async fn finish_streaming_job(&self, job_id: ObjectId) -> MetaResult { - let inner = self.inner.write().await; - let txn = inner.db.begin().await?; - - let job_type = Object::find_by_id(job_id) - .select_only() - .column(object::Column::ObjType) - .into_tuple() - .one(&txn) - .await? - .ok_or_else(|| MetaError::catalog_id_not_found("streaming job", job_id))?; - - // update `created_at` as now() and `created_at_cluster_version` as current cluster version. - let res = Object::update_many() - .col_expr(object::Column::CreatedAt, Expr::current_timestamp().into()) - .col_expr( - object::Column::CreatedAtClusterVersion, - current_cluster_version().into(), - ) - .filter(object::Column::Oid.eq(job_id)) - .exec(&txn) - .await?; - if res.rows_affected == 0 { - return Err(MetaError::catalog_id_not_found("streaming job", job_id)); - } - - // mark the target stream job as `Created`. - let job = streaming_job::ActiveModel { - job_id: Set(job_id), - job_status: Set(JobStatus::Created), - ..Default::default() - }; - job.update(&txn).await?; - - // notify frontend: job, internal tables. - let internal_table_objs = Table::find() - .find_also_related(Object) - .filter(table::Column::BelongsToJobId.eq(job_id)) - .all(&txn) - .await?; - let mut relations = internal_table_objs - .iter() - .map(|(table, obj)| PbRelation { - relation_info: Some(PbRelationInfo::Table( - ObjectModel(table.clone(), obj.clone().unwrap()).into(), - )), - }) - .collect_vec(); - - match job_type { - ObjectType::Table => { - let (table, obj) = Table::find_by_id(job_id) - .find_also_related(Object) - .one(&txn) - .await? - .ok_or_else(|| MetaError::catalog_id_not_found("table", job_id))?; - if let Some(source_id) = table.optional_associated_source_id { - let (src, obj) = Source::find_by_id(source_id) - .find_also_related(Object) - .one(&txn) - .await? - .ok_or_else(|| MetaError::catalog_id_not_found("source", source_id))?; - relations.push(PbRelation { - relation_info: Some(PbRelationInfo::Source( - ObjectModel(src, obj.unwrap()).into(), - )), - }); - } - relations.push(PbRelation { - relation_info: Some(PbRelationInfo::Table( - ObjectModel(table, obj.unwrap()).into(), - )), - }); - } - ObjectType::Sink => { - let (sink, obj) = Sink::find_by_id(job_id) - .find_also_related(Object) - .one(&txn) - .await? - .ok_or_else(|| MetaError::catalog_id_not_found("sink", job_id))?; - relations.push(PbRelation { - relation_info: Some(PbRelationInfo::Sink( - ObjectModel(sink, obj.unwrap()).into(), - )), - }); - } - ObjectType::Subscription => { - let (mut subscription, obj) = Subscription::find_by_id(job_id) - .find_also_related(Object) - .one(&txn) - .await? - .ok_or_else(|| MetaError::catalog_id_not_found("subscription", job_id))?; - let log_store_names: Vec<_> = internal_table_objs - .iter() - .filter(|a| is_subscription_internal_table(&subscription.name, &a.0.name)) - .map(|a| &a.0.name) - .collect(); - if log_store_names.len() != 1 { - bail!("A subscription can only have one log_store_name"); - } - subscription.subscription_internal_table_name = - log_store_names.get(0).cloned().cloned(); - relations.push(PbRelation { - relation_info: Some(PbRelationInfo::Subscription( - ObjectModel(subscription, obj.unwrap()).into(), - )), - }); - } - ObjectType::Index => { - let (index, obj) = Index::find_by_id(job_id) - .find_also_related(Object) - .one(&txn) - .await? - .ok_or_else(|| MetaError::catalog_id_not_found("index", job_id))?; - { - let (table, obj) = Table::find_by_id(index.index_table_id) - .find_also_related(Object) - .one(&txn) - .await? - .ok_or_else(|| { - MetaError::catalog_id_not_found("table", index.index_table_id) - })?; - relations.push(PbRelation { - relation_info: Some(PbRelationInfo::Table( - ObjectModel(table, obj.unwrap()).into(), - )), - }); - } - relations.push(PbRelation { - relation_info: Some(PbRelationInfo::Index( - ObjectModel(index, obj.unwrap()).into(), - )), - }); - } - ObjectType::Source => { - let (source, obj) = Source::find_by_id(job_id) - .find_also_related(Object) - .one(&txn) - .await? - .ok_or_else(|| MetaError::catalog_id_not_found("source", job_id))?; - relations.push(PbRelation { - relation_info: Some(PbRelationInfo::Source( - ObjectModel(source, obj.unwrap()).into(), - )), - }); - } - _ => unreachable!("invalid job type: {:?}", job_type), - } - - let fragment_mapping = get_fragment_mappings(&txn, job_id).await?; - txn.commit().await?; - - self.notify_fragment_mapping(NotificationOperation::Add, fragment_mapping) - .await; - let version = self - .notify_frontend( - NotificationOperation::Add, - NotificationInfo::RelationGroup(PbRelationGroup { relations }), - ) - .await; - - Ok(version) - } - pub async fn create_source( &self, mut pb_source: PbSource, @@ -1104,6 +1091,88 @@ impl CatalogController { Ok(version) } + pub async fn create_secret(&self, mut pb_secret: PbSecret) -> MetaResult { + let inner = self.inner.write().await; + let owner_id = pb_secret.owner as _; + let txn = inner.db.begin().await?; + ensure_user_id(owner_id, &txn).await?; + ensure_object_id(ObjectType::Database, pb_secret.database_id as _, &txn).await?; + ensure_object_id(ObjectType::Schema, pb_secret.schema_id as _, &txn).await?; + check_secret_name_duplicate(&pb_secret, &txn).await?; + + let secret_obj = Self::create_object( + &txn, + ObjectType::Secret, + owner_id, + Some(pb_secret.database_id as _), + Some(pb_secret.schema_id as _), + ) + .await?; + pb_secret.id = secret_obj.oid as _; + let secret: secret::ActiveModel = pb_secret.clone().into(); + Secret::insert(secret).exec(&txn).await?; + + txn.commit().await?; + + let version = self + .notify_frontend( + NotificationOperation::Add, + NotificationInfo::Secret(pb_secret), + ) + .await; + Ok(version) + } + + pub async fn get_secret_by_id(&self, secret_id: SecretId) -> MetaResult { + let inner = self.inner.read().await; + let (secret, obj) = Secret::find_by_id(secret_id) + .find_also_related(Object) + .one(&inner.db) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("secret", secret_id))?; + Ok(ObjectModel(secret, obj.unwrap()).into()) + } + + pub async fn drop_secret(&self, secret_id: SecretId) -> MetaResult { + let inner = self.inner.write().await; + let txn = inner.db.begin().await?; + let (secret, secret_obj) = Secret::find_by_id(secret_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("secret", secret_id))?; + ensure_object_not_refer(ObjectType::Secret, secret_id, &txn).await?; + + // Find affect users with privileges on the connection. + let to_update_user_ids: Vec = UserPrivilege::find() + .select_only() + .distinct() + .column(user_privilege::Column::UserId) + .filter(user_privilege::Column::Oid.eq(secret_id)) + .into_tuple() + .all(&txn) + .await?; + + let res = Object::delete_by_id(secret_id).exec(&txn).await?; + if res.rows_affected == 0 { + return Err(MetaError::catalog_id_not_found("secret", secret_id)); + } + let user_infos = list_user_info_by_ids(to_update_user_ids, &txn).await?; + + txn.commit().await?; + + let pb_secret: PbSecret = ObjectModel(secret, secret_obj.unwrap()).into(); + + self.notify_users_update(user_infos).await; + let version = self + .notify_frontend( + NotificationOperation::Delete, + NotificationInfo::Secret(pb_secret), + ) + .await; + Ok(version) + } + pub async fn create_connection( &self, mut pb_connection: PbConnection, @@ -1446,35 +1515,6 @@ impl CatalogController { relations.push(PbRelationInfo::Subscription( ObjectModel(subscription, obj).into(), )); - - // internal tables. - let internal_tables: Vec = Table::find() - .select_only() - .column(table::Column::TableId) - .filter(table::Column::BelongsToJobId.eq(object_id)) - .into_tuple() - .all(&txn) - .await?; - - Object::update_many() - .col_expr( - object::Column::OwnerId, - SimpleExpr::Value(Value::Int(Some(new_owner))), - ) - .filter(object::Column::Oid.is_in(internal_tables.clone())) - .exec(&txn) - .await?; - - let table_objs = Table::find() - .find_also_related(Object) - .filter(table::Column::TableId.is_in(internal_tables)) - .all(&txn) - .await?; - for (table, table_obj) in table_objs { - relations.push(PbRelationInfo::Table( - ObjectModel(table, table_obj.unwrap()).into(), - )); - } } ObjectType::View => { let view = View::find_by_id(object_id) @@ -1706,37 +1746,6 @@ impl CatalogController { relations.push(PbRelationInfo::Subscription( ObjectModel(subscription, obj).into(), )); - - // internal tables. - let internal_tables: Vec = Table::find() - .select_only() - .column(table::Column::TableId) - .filter(table::Column::BelongsToJobId.eq(object_id)) - .into_tuple() - .all(&txn) - .await?; - - if !internal_tables.is_empty() { - Object::update_many() - .col_expr( - object::Column::SchemaId, - SimpleExpr::Value(Value::Int(Some(new_schema))), - ) - .filter(object::Column::Oid.is_in(internal_tables.clone())) - .exec(&txn) - .await?; - - let table_objs = Table::find() - .find_also_related(Object) - .filter(table::Column::TableId.is_in(internal_tables)) - .all(&txn) - .await?; - for (table, table_obj) in table_objs { - relations.push(PbRelationInfo::Table( - ObjectModel(table, table_obj.unwrap()).into(), - )); - } - } } ObjectType::View => { let view = View::find_by_id(object_id) @@ -2061,8 +2070,9 @@ impl CatalogController { to_drop_objects.extend(to_drop_internal_table_objs); } - let (source_fragments, removed_actors) = + let (source_fragments, removed_actors, removed_fragments) = resolve_source_register_info_for_jobs(&txn, to_drop_streaming_jobs.clone()).await?; + let fragment_mappings = get_fragment_mappings_by_jobs(&txn, to_drop_streaming_jobs.clone()).await?; @@ -2163,6 +2173,17 @@ impl CatalogController { NotificationInfo::RelationGroup(PbRelationGroup { relations }), ) .await; + + let fragment_mappings = fragment_mappings + .into_iter() + .map( + |FragmentParallelUnitMapping { fragment_id, .. }| PbFragmentWorkerSlotMapping { + fragment_id, + mapping: None, + }, + ) + .collect(); + self.notify_fragment_mapping(NotificationOperation::Delete, fragment_mappings) .await; @@ -2174,6 +2195,7 @@ impl CatalogController { connections: vec![], source_fragments, removed_actors, + removed_fragments, }, version, )) @@ -2492,6 +2514,11 @@ impl CatalogController { inner.list_databases().await } + pub async fn list_schemas(&self) -> MetaResult> { + let inner = self.inner.read().await; + inner.list_schemas().await + } + pub async fn list_all_state_tables(&self) -> MetaResult> { let inner = self.inner.read().await; inner.list_all_state_tables().await @@ -2536,6 +2563,19 @@ impl CatalogController { Ok(table_ids) } + pub async fn list_view_ids(&self, schema_id: SchemaId) -> MetaResult> { + let inner = self.inner.read().await; + let view_ids: Vec = View::find() + .select_only() + .column(view::Column::ViewId) + .join(JoinType::InnerJoin, view::Relation::Object.def()) + .filter(object::Column::SchemaId.eq(schema_id)) + .into_tuple() + .all(&inner.db) + .await?; + Ok(view_ids) + } + pub async fn list_tables_by_type(&self, table_type: TableType) -> MetaResult> { let inner = self.inner.read().await; let table_objs = Table::find() @@ -2582,6 +2622,11 @@ impl CatalogController { inner.list_views().await } + pub async fn list_users(&self) -> MetaResult> { + let inner = self.inner.read().await; + inner.list_users().await + } + pub async fn get_table_by_name( &self, database_name: &str, @@ -2590,7 +2635,7 @@ impl CatalogController { let inner = self.inner.read().await; let table_obj = Table::find() .find_also_related(Object) - .join(JoinType::InnerJoin, object::Relation::Database.def()) + .join(JoinType::InnerJoin, object::Relation::Database2.def()) .filter( table::Column::Name .eq(table_name) @@ -2614,6 +2659,25 @@ impl CatalogController { .collect()) } + pub async fn get_subscription_by_id( + &self, + subscription_id: SubscriptionId, + ) -> MetaResult { + let inner = self.inner.read().await; + let subscription_objs = Subscription::find() + .find_also_related(Object) + .filter(subscription::Column::SubscriptionId.eq(subscription_id)) + .all(&inner.db) + .await?; + let subscription: PbSubscription = subscription_objs + .into_iter() + .map(|(subscription, obj)| ObjectModel(subscription, obj.unwrap()).into()) + .find_or_first(|_| true) + .ok_or_else(|| anyhow!("cant find subscription with id {}", subscription_id))?; + + Ok(subscription) + } + pub async fn find_creating_streaming_job_ids( &self, infos: Vec, @@ -2757,11 +2821,9 @@ impl CatalogController { let inner = self.inner.read().await; // created table ids. - let mut table_ids: Vec = Table::find() + let mut table_ids: Vec = StreamingJob::find() .select_only() - .column(table::Column::TableId) - .join(JoinType::LeftJoin, table::Relation::Object1.def()) - .join(JoinType::LeftJoin, object::Relation::StreamingJob.def()) + .column(streaming_job::Column::JobId) .filter(streaming_job::Column::JobStatus.eq(JobStatus::Created)) .into_tuple() .all(&inner.db) @@ -2805,6 +2867,7 @@ impl CatalogControllerInner { let views = self.list_views().await?; let functions = self.list_functions().await?; let connections = self.list_connections().await?; + let secrets = self.list_secrets().await?; let users = self.list_users().await?; @@ -2820,6 +2883,7 @@ impl CatalogControllerInner { views, functions, connections, + secrets, ), users, )) @@ -2993,8 +3057,6 @@ impl CatalogControllerInner { async fn list_subscriptions(&self) -> MetaResult> { let subscription_objs = Subscription::find() .find_also_related(Object) - .join(JoinType::LeftJoin, object::Relation::StreamingJob.def()) - .filter(streaming_job::Column::JobStatus.eq(JobStatus::Created)) .all(&self.db) .await?; @@ -3040,6 +3102,17 @@ impl CatalogControllerInner { .collect()) } + async fn list_secrets(&self) -> MetaResult> { + let secret_objs = Secret::find() + .find_also_related(Object) + .all(&self.db) + .await?; + Ok(secret_objs + .into_iter() + .map(|(secret, obj)| ObjectModel(secret, obj.unwrap()).into()) + .collect()) + } + async fn list_functions(&self) -> MetaResult> { let func_objs = Function::find() .find_also_related(Object) @@ -3056,7 +3129,6 @@ impl CatalogControllerInner { #[cfg(test)] #[cfg(not(madsim))] mod tests { - use risingwave_meta_model_v2::ViewId; use super::*; diff --git a/src/meta/src/controller/cluster.rs b/src/meta/src/controller/cluster.rs index 01bf28643bb66..50300f0ec2827 100644 --- a/src/meta/src/controller/cluster.rs +++ b/src/meta/src/controller/cluster.rs @@ -175,7 +175,7 @@ impl ClusterController { Ok(()) } - pub async fn delete_worker(&self, host_address: HostAddress) -> MetaResult { + pub async fn delete_worker(&self, host_address: HostAddress) -> MetaResult { let mut inner = self.inner.write().await; let worker = inner.delete_worker(host_address).await?; if worker.r#type() == PbWorkerType::ComputeNode { @@ -262,11 +262,12 @@ impl ClusterController { // 3. Delete expired workers. let worker_infos = match Worker::find() .select_only() + .column(worker::Column::WorkerId) .column(worker::Column::WorkerType) .column(worker::Column::Host) .column(worker::Column::Port) .filter(worker::Column::WorkerId.is_in(worker_to_delete.clone())) - .into_tuple::<(WorkerType, String, i32)>() + .into_tuple::<(WorkerId, WorkerType, String, i32)>() .all(&inner.db) .await { @@ -276,33 +277,36 @@ impl ClusterController { continue; } }; + drop(inner); - if let Err(err) = Worker::delete_many() - .filter(worker::Column::WorkerId.is_in(worker_to_delete)) - .exec(&inner.db) - .await - { - tracing::warn!(error = %err.as_report(), "Failed to delete expire workers from db"); - continue; - } - - for (worker_type, host, port) in worker_infos { - match worker_type { - WorkerType::Frontend - | WorkerType::ComputeNode - | WorkerType::Compactor - | WorkerType::RiseCtl => { - cluster_controller - .env - .notification_manager() - .delete_sender( - worker_type.into(), - WorkerKey(HostAddress { host, port }), - ) - .await + for (worker_id, worker_type, host, port) in worker_infos { + let host_addr = PbHostAddress { host, port }; + match cluster_controller.delete_worker(host_addr.clone()).await { + Ok(_) => { + tracing::warn!( + worker_id, + ?host_addr, + %now, + "Deleted expired worker" + ); + match worker_type { + WorkerType::Frontend + | WorkerType::ComputeNode + | WorkerType::Compactor + | WorkerType::RiseCtl => { + cluster_controller + .env + .notification_manager() + .delete_sender(worker_type.into(), WorkerKey(host_addr)) + .await + } + _ => {} + }; } - _ => {} - }; + Err(err) => { + tracing::warn!(error = %err.as_report(), "Failed to delete expire worker from db"); + } + } } } }); diff --git a/src/meta/src/controller/fragment.rs b/src/meta/src/controller/fragment.rs index 552008914d76e..e88046ed17467 100644 --- a/src/meta/src/controller/fragment.rs +++ b/src/meta/src/controller/fragment.rs @@ -12,12 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::hash_map::Entry; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::mem::swap; use anyhow::Context; use itertools::Itertools; use risingwave_common::bail; +use risingwave_common::hash::ParallelUnitMapping; use risingwave_common::util::stream_graph_visitor::visit_stream_node; use risingwave_meta_model_v2::actor::ActorStatus; use risingwave_meta_model_v2::prelude::{Actor, ActorDispatcher, Fragment, Sink, StreamingJob}; @@ -34,7 +36,7 @@ use risingwave_pb::meta::table_fragments::actor_status::PbActorState; use risingwave_pb::meta::table_fragments::fragment::PbFragmentDistributionType; use risingwave_pb::meta::table_fragments::{PbActorStatus, PbFragment, PbState}; use risingwave_pb::meta::{ - FragmentParallelUnitMapping, PbFragmentParallelUnitMapping, PbTableFragments, + FragmentWorkerSlotMapping, PbFragmentWorkerSlotMapping, PbTableFragments, }; use risingwave_pb::source::PbConnectorSplits; use risingwave_pb::stream_plan::stream_node::NodeBody; @@ -50,10 +52,11 @@ use sea_orm::{ use crate::controller::catalog::{CatalogController, CatalogControllerInner}; use crate::controller::utils::{ - get_actor_dispatchers, FragmentDesc, PartialActorLocation, PartialFragmentStateTables, + get_actor_dispatchers, get_parallel_unit_to_worker_map, FragmentDesc, PartialActorLocation, + PartialFragmentStateTables, }; -use crate::manager::{ActorInfos, LocalNotification}; -use crate::model::TableParallelism; +use crate::manager::{ActorInfos, InflightFragmentInfo, LocalNotification}; +use crate::model::{TableFragments, TableParallelism}; use crate::stream::SplitAssignment; use crate::{MetaError, MetaResult}; @@ -61,7 +64,9 @@ impl CatalogControllerInner { /// List all fragment vnode mapping info for all CREATED streaming jobs. pub async fn all_running_fragment_mappings( &self, - ) -> MetaResult + '_> { + ) -> MetaResult + '_> { + let txn = self.db.begin().await?; + let fragment_mappings: Vec<(FragmentId, FragmentVnodeMapping)> = Fragment::find() .join(JoinType::InnerJoin, fragment::Relation::Object.def()) .join(JoinType::InnerJoin, object::Relation::StreamingJob.def()) @@ -69,14 +74,17 @@ impl CatalogControllerInner { .columns([fragment::Column::FragmentId, fragment::Column::VnodeMapping]) .filter(streaming_job::Column::JobStatus.eq(JobStatus::Created)) .into_tuple() - .all(&self.db) + .all(&txn) .await?; - Ok(fragment_mappings.into_iter().map(|(fragment_id, mapping)| { - FragmentParallelUnitMapping { - fragment_id: fragment_id as _, - mapping: Some(mapping.to_protobuf()), - } - })) + + let parallel_unit_to_worker = get_parallel_unit_to_worker_map(&txn).await?; + + let mappings = CatalogController::convert_fragment_mappings( + fragment_mappings, + ¶llel_unit_to_worker, + )?; + + Ok(mappings.into_iter()) } } @@ -84,7 +92,7 @@ impl CatalogController { pub(crate) async fn notify_fragment_mapping( &self, operation: NotificationOperation, - fragment_mappings: Vec, + fragment_mappings: Vec, ) { let fragment_ids = fragment_mappings .iter() @@ -96,7 +104,7 @@ impl CatalogController { .notification_manager() .notify_frontend( operation, - NotificationInfo::ParallelUnitMapping(fragment_mapping), + NotificationInfo::StreamingWorkerSlotMapping(fragment_mapping), ) .await; } @@ -765,16 +773,6 @@ impl CatalogController { Ok(table_fragments) } - /// Check if the fragment type mask is injectable. - fn is_injectable(fragment_type_mask: u32) -> bool { - (fragment_type_mask - & (PbFragmentTypeFlag::Source as u32 - | PbFragmentTypeFlag::Now as u32 - | PbFragmentTypeFlag::Values as u32 - | PbFragmentTypeFlag::BarrierRecv as u32)) - != 0 - } - pub async fn list_actor_locations(&self) -> MetaResult> { let inner = self.inner.read().await; let actor_locations: Vec = @@ -859,37 +857,54 @@ impl CatalogController { /// collected pub async fn load_all_actors(&self) -> MetaResult { let inner = self.inner.read().await; - let actor_info: Vec<(ActorId, WorkerId, i32)> = Actor::find() + let actor_info: Vec<(ActorId, WorkerId, FragmentId, i32, I32Array)> = Actor::find() .select_only() .column(actor::Column::ActorId) .column(actor::Column::WorkerId) + .column(fragment::Column::FragmentId) .column(fragment::Column::FragmentTypeMask) + .column(fragment::Column::StateTableIds) .join(JoinType::InnerJoin, actor::Relation::Fragment.def()) .filter(actor::Column::Status.eq(ActorStatus::Running)) .into_tuple() .all(&inner.db) .await?; - let mut actor_maps = HashMap::new(); - let mut barrier_inject_actor_maps = HashMap::new(); - - for (actor_id, worker_id, type_mask) in actor_info { - actor_maps - .entry(worker_id as _) - .or_insert_with(Vec::new) - .push(actor_id as _); - if Self::is_injectable(type_mask as _) { - barrier_inject_actor_maps - .entry(worker_id as _) - .or_insert_with(Vec::new) - .push(actor_id as _); + let mut fragment_infos = HashMap::new(); + + for (actor_id, worker_id, fragment_id, type_mask, state_table_ids) in actor_info { + let state_table_ids = state_table_ids.into_inner(); + match fragment_infos.entry(fragment_id as crate::model::FragmentId) { + Entry::Occupied(mut entry) => { + let info: &mut InflightFragmentInfo = entry.get_mut(); + debug_assert_eq!( + info.state_table_ids, + state_table_ids + .into_iter() + .map(|table_id| risingwave_common::catalog::TableId::new(table_id as _)) + .collect() + ); + assert!(info.actors.insert(actor_id as _, worker_id as _).is_none()); + assert_eq!( + info.is_injectable, + TableFragments::is_injectable(type_mask as _) + ); + } + Entry::Vacant(entry) => { + let state_table_ids = state_table_ids + .into_iter() + .map(|table_id| risingwave_common::catalog::TableId::new(table_id as _)) + .collect(); + entry.insert(InflightFragmentInfo { + actors: HashMap::from_iter([(actor_id as _, worker_id as _)]), + state_table_ids, + is_injectable: TableFragments::is_injectable(type_mask as _), + }); + } } } - Ok(ActorInfos { - actor_maps, - barrier_inject_actor_maps, - }) + Ok(ActorInfos::new(fragment_infos)) } pub async fn migrate_actors(&self, plan: HashMap) -> MetaResult<()> { @@ -936,23 +951,39 @@ impl CatalogController { .await?; } + let parallel_unit_to_worker = get_parallel_unit_to_worker_map(&txn).await?; + + let fragment_worker_slot_mapping = + Self::convert_fragment_mappings(fragment_mapping, ¶llel_unit_to_worker)?; + txn.commit().await?; - self.notify_fragment_mapping( - NotificationOperation::Update, - fragment_mapping - .into_iter() - .map(|(fragment_id, mapping)| PbFragmentParallelUnitMapping { - fragment_id: fragment_id as _, - mapping: Some(mapping.to_protobuf()), - }) - .collect(), - ) - .await; + self.notify_fragment_mapping(NotificationOperation::Update, fragment_worker_slot_mapping) + .await; Ok(()) } + pub(crate) fn convert_fragment_mappings( + fragment_mappings: Vec<(FragmentId, FragmentVnodeMapping)>, + parallel_unit_to_worker: &HashMap, + ) -> MetaResult> { + let mut result = vec![]; + + for (fragment_id, mapping) in fragment_mappings { + result.push(PbFragmentWorkerSlotMapping { + fragment_id: fragment_id as _, + mapping: Some( + ParallelUnitMapping::from_protobuf(&mapping.to_protobuf()) + .to_worker_slot(parallel_unit_to_worker)? + .to_protobuf(), + ), + }) + } + + Ok(result) + } + pub async fn all_inuse_parallel_units(&self) -> MetaResult> { let inner = self.inner.read().await; let parallel_units: Vec = Actor::find() @@ -1346,7 +1377,6 @@ impl CatalogController { #[cfg(test)] mod tests { use std::collections::{BTreeMap, HashMap}; - use std::default::Default; use itertools::Itertools; use risingwave_common::hash::{ParallelUnitId, ParallelUnitMapping}; diff --git a/src/meta/src/controller/mod.rs b/src/meta/src/controller/mod.rs index a271ab85bb928..5ace222739d52 100644 --- a/src/meta/src/controller/mod.rs +++ b/src/meta/src/controller/mod.rs @@ -12,17 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::BTreeMap; + use anyhow::anyhow; use risingwave_common::util::epoch::Epoch; use risingwave_meta_model_v2::{ - connection, database, function, index, object, schema, sink, source, subscription, table, view, + connection, database, function, index, object, schema, secret, sink, source, subscription, + table, view, }; use risingwave_pb::catalog::connection::PbInfo as PbConnectionInfo; use risingwave_pb::catalog::source::PbOptionalAssociatedTableId; +use risingwave_pb::catalog::subscription::PbSubscriptionState; use risingwave_pb::catalog::table::{PbOptionalAssociatedSourceId, PbTableType}; use risingwave_pb::catalog::{ PbConnection, PbCreateType, PbDatabase, PbFunction, PbHandleConflictBehavior, PbIndex, - PbSchema, PbSink, PbSinkType, PbSource, PbStreamJobStatus, PbSubscription, PbTable, PbView, + PbSchema, PbSecret, PbSink, PbSinkType, PbSource, PbStreamJobStatus, PbSubscription, PbTable, + PbView, }; use sea_orm::{DatabaseConnection, ModelTrait}; @@ -81,6 +86,19 @@ impl From> for PbDatabase { } } +impl From> for PbSecret { + fn from(value: ObjectModel) -> Self { + Self { + id: value.0.secret_id as _, + name: value.0.name, + database_id: value.1.database_id.unwrap() as _, + value: value.0.value, + owner: value.1.owner_id as _, + schema_id: value.1.schema_id.unwrap() as _, + } + } +} + impl From> for PbSchema { fn from(value: ObjectModel) -> Self { Self { @@ -125,10 +143,10 @@ impl From> for PbTable { .cardinality .map(|cardinality| cardinality.to_protobuf()), initialized_at_epoch: Some( - Epoch::from_unix_millis(value.1.initialized_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.initialized_at.and_utc().timestamp_millis() as _).0, ), created_at_epoch: Some( - Epoch::from_unix_millis(value.1.created_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.created_at.and_utc().timestamp_millis() as _).0, ), cleaned_by_watermark: value.0.cleaned_by_watermark, stream_job_status: PbStreamJobStatus::Created as _, // todo: deprecate it. @@ -149,6 +167,10 @@ impl From> for PbTable { impl From> for PbSource { fn from(value: ObjectModel) -> Self { + let mut secret_ref_map = BTreeMap::new(); + if let Some(secret_ref) = value.0.secret_ref { + secret_ref_map = secret_ref.to_protobuf(); + } Self { id: value.0.source_id as _, schema_id: value.1.schema_id.unwrap() as _, @@ -165,10 +187,10 @@ impl From> for PbSource { connection_id: value.0.connection_id.map(|id| id as _), // todo: using the timestamp from the database directly. initialized_at_epoch: Some( - Epoch::from_unix_millis(value.1.initialized_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.initialized_at.and_utc().timestamp_millis() as _).0, ), created_at_epoch: Some( - Epoch::from_unix_millis(value.1.created_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.created_at.and_utc().timestamp_millis() as _).0, ), version: value.0.version as _, optional_associated_table_id: value @@ -177,12 +199,17 @@ impl From> for PbSource { .map(|id| PbOptionalAssociatedTableId::AssociatedTableId(id as _)), initialized_at_cluster_version: value.1.initialized_at_cluster_version, created_at_cluster_version: value.1.created_at_cluster_version, + secret_refs: secret_ref_map, } } } impl From> for PbSink { fn from(value: ObjectModel) -> Self { + let mut secret_ref_map = BTreeMap::new(); + if let Some(secret_ref) = value.0.secret_ref { + secret_ref_map = secret_ref.to_protobuf(); + } Self { id: value.0.sink_id as _, schema_id: value.1.schema_id.unwrap() as _, @@ -199,10 +226,10 @@ impl From> for PbSink { definition: value.0.definition, connection_id: value.0.connection_id.map(|id| id as _), initialized_at_epoch: Some( - Epoch::from_unix_millis(value.1.initialized_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.initialized_at.and_utc().timestamp_millis() as _).0, ), created_at_epoch: Some( - Epoch::from_unix_millis(value.1.created_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.created_at.and_utc().timestamp_millis() as _).0, ), db_name: value.0.db_name, sink_from_name: value.0.sink_from_name, @@ -212,6 +239,7 @@ impl From> for PbSink { initialized_at_cluster_version: value.1.initialized_at_cluster_version, created_at_cluster_version: value.1.created_at_cluster_version, create_type: PbCreateType::Foreground as _, + secret_refs: secret_ref_map, } } } @@ -223,24 +251,19 @@ impl From> for PbSubscription { schema_id: value.1.schema_id.unwrap() as _, database_id: value.1.database_id.unwrap() as _, name: value.0.name, - plan_pk: value.0.plan_pk.to_protobuf(), - dependent_relations: vec![], // todo: deprecate it. - distribution_key: value.0.distribution_key.0, owner: value.1.owner_id as _, - properties: value.0.properties.0, + retention_seconds: value.0.retention_seconds as _, definition: value.0.definition, initialized_at_epoch: Some( - Epoch::from_unix_millis(value.1.initialized_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.initialized_at.and_utc().timestamp_millis() as _).0, ), created_at_epoch: Some( - Epoch::from_unix_millis(value.1.created_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.created_at.and_utc().timestamp_millis() as _).0, ), - stream_job_status: PbStreamJobStatus::Created as _, // todo: deprecate it. - column_catalogs: value.0.columns.to_protobuf(), - subscription_from_name: value.0.subscription_from_name, initialized_at_cluster_version: value.1.initialized_at_cluster_version, created_at_cluster_version: value.1.created_at_cluster_version, - subscription_internal_table_name: value.0.subscription_internal_table_name, + dependent_table_id: value.0.dependent_table_id as _, + subscription_state: PbSubscriptionState::Init as _, } } } @@ -256,12 +279,13 @@ impl From> for PbIndex { index_table_id: value.0.index_table_id as _, primary_table_id: value.0.primary_table_id as _, index_item: value.0.index_items.to_protobuf(), + index_column_properties: value.0.index_column_properties.to_protobuf(), index_columns_len: value.0.index_columns_len as _, initialized_at_epoch: Some( - Epoch::from_unix_millis(value.1.initialized_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.initialized_at.and_utc().timestamp_millis() as _).0, ), created_at_epoch: Some( - Epoch::from_unix_millis(value.1.created_at.timestamp_millis() as _).0, + Epoch::from_unix_millis(value.1.created_at.and_utc().timestamp_millis() as _).0, ), stream_job_status: PbStreamJobStatus::Created as _, // todo: deprecate it. initialized_at_cluster_version: value.1.initialized_at_cluster_version, diff --git a/src/meta/src/controller/rename.rs b/src/meta/src/controller/rename.rs index 860981762cde4..3947413ba8689 100644 --- a/src/meta/src/controller/rename.rs +++ b/src/meta/src/controller/rename.rs @@ -153,7 +153,12 @@ impl QueryRewriter<'_> { fn visit_query(&self, query: &mut Query) { if let Some(with) = &mut query.with { for cte_table in &mut with.cte_tables { - self.visit_query(&mut cte_table.query); + match &mut cte_table.cte_inner { + risingwave_sqlparser::ast::CteInner::Query(query) => self.visit_query(query), + risingwave_sqlparser::ast::CteInner::ChangeLog(from) => { + *from = Ident::new_unchecked(self.to) + } + } } } self.visit_set_expr(&mut query.body); @@ -170,7 +175,7 @@ impl QueryRewriter<'_> { /// /// So that we DON'T have to: /// 1. rewrite the select and expr part like `schema.table.column`, `table.column`, - /// `alias.column` etc. + /// `alias.column` etc. /// 2. handle the case that the old name is used as alias. /// 3. handle the case that the new name is used as alias. fn visit_table_factor(&self, table_factor: &mut TableFactor) { diff --git a/src/meta/src/controller/session_params.rs b/src/meta/src/controller/session_params.rs index 4a27967fa2b0a..566170a0ef4d2 100644 --- a/src/meta/src/controller/session_params.rs +++ b/src/meta/src/controller/session_params.rs @@ -151,7 +151,7 @@ mod tests { use sea_orm::QueryFilter; let env = MetaSrvEnv::for_test_with_sql_meta_store().await; - let meta_store = env.meta_store().as_sql(); + let meta_store = env.meta_store_ref().as_sql(); let init_params = SessionConfig::default(); // init system parameter controller as first launch. diff --git a/src/meta/src/controller/streaming_job.rs b/src/meta/src/controller/streaming_job.rs index 271860ee49561..2db28e153e59b 100644 --- a/src/meta/src/controller/streaming_job.rs +++ b/src/meta/src/controller/streaming_job.rs @@ -16,11 +16,11 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::num::NonZeroUsize; use itertools::Itertools; -use risingwave_common::bail; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::hash::{ActorMapping, ParallelUnitId, ParallelUnitMapping}; use risingwave_common::util::column_index_mapping::ColIndexMapping; use risingwave_common::util::stream_graph_visitor::visit_stream_node; +use risingwave_common::{bail, current_cluster_version}; use risingwave_meta_model_v2::actor::ActorStatus; use risingwave_meta_model_v2::actor_dispatcher::DispatcherType; use risingwave_meta_model_v2::object::ObjectType; @@ -30,9 +30,9 @@ use risingwave_meta_model_v2::prelude::{ }; use risingwave_meta_model_v2::{ actor, actor_dispatcher, fragment, index, object, object_dependency, sink, source, - streaming_job, subscription, table, ActorId, ActorUpstreamActors, CreateType, DatabaseId, - ExprNodeArray, FragmentId, I32Array, IndexId, JobStatus, ObjectId, SchemaId, SourceId, - StreamNode, StreamingParallelism, TableId, TableVersion, UserId, + streaming_job, table, ActorId, ActorUpstreamActors, CreateType, DatabaseId, ExprNodeArray, + FragmentId, I32Array, IndexId, JobStatus, ObjectId, SchemaId, SourceId, StreamNode, + StreamingParallelism, TableId, TableVersion, UserId, }; use risingwave_pb::catalog::source::PbOptionalAssociatedTableId; use risingwave_pb::catalog::table::{PbOptionalAssociatedSourceId, PbTableVersion}; @@ -43,16 +43,17 @@ use risingwave_pb::meta::subscribe_response::{ }; use risingwave_pb::meta::table_fragments::PbActorStatus; use risingwave_pb::meta::{ - FragmentParallelUnitMapping, PbRelation, PbRelationGroup, PbTableFragments, + FragmentWorkerSlotMapping, PbFragmentWorkerSlotMapping, PbRelation, PbRelationGroup, + PbTableFragments, Relation, }; use risingwave_pb::source::{PbConnectorSplit, PbConnectorSplits}; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; use risingwave_pb::stream_plan::stream_node::PbNodeBody; -use risingwave_pb::stream_plan::update_mutation::PbMergeUpdate; +use risingwave_pb::stream_plan::update_mutation::{MergeUpdate, PbMergeUpdate}; use risingwave_pb::stream_plan::{ PbDispatcher, PbDispatcherType, PbFragmentTypeFlag, PbStreamActor, }; -use sea_orm::sea_query::SimpleExpr; +use sea_orm::sea_query::{Expr, Query, SimpleExpr}; use sea_orm::ActiveValue::Set; use sea_orm::{ ActiveEnum, ActiveModelTrait, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, @@ -65,7 +66,7 @@ use crate::controller::catalog::CatalogController; use crate::controller::rename::ReplaceTableExprRewriter; use crate::controller::utils::{ check_relation_name_duplicate, check_sink_into_table_cycle, ensure_object_id, ensure_user_id, - get_fragment_actor_ids, get_fragment_mappings, + get_fragment_actor_ids, get_fragment_mappings, get_parallel_unit_to_worker_map, }; use crate::controller::ObjectModel; use crate::manager::{NotificationVersion, SinkId, StreamingJob}; @@ -123,6 +124,39 @@ impl CatalogController { ) .await?; + // check if any dependent relation is in altering status. + let dependent_relations = streaming_job.dependent_relations(); + if !dependent_relations.is_empty() { + let altering_cnt = ObjectDependency::find() + .join( + JoinType::InnerJoin, + object_dependency::Relation::Object1.def(), + ) + .join(JoinType::InnerJoin, object::Relation::StreamingJob.def()) + .filter( + object_dependency::Column::Oid + .is_in(dependent_relations.iter().map(|id| *id as ObjectId)) + .and(object::Column::ObjType.eq(ObjectType::Table)) + .and(streaming_job::Column::JobStatus.ne(JobStatus::Created)) + .and( + // It means the referring table is just dummy for altering. + object::Column::Oid.not_in_subquery( + Query::select() + .column(table::Column::TableId) + .from(Table) + .to_owned(), + ), + ), + ) + .count(&txn) + .await?; + if altering_cnt != 0 { + return Err(MetaError::permission_denied( + "some dependent relations are being altered", + )); + } + } + match streaming_job { StreamingJob::MaterializedView(table) => { let job_id = Self::create_streaming_job_obj( @@ -171,22 +205,6 @@ impl CatalogController { let sink: sink::ActiveModel = sink.clone().into(); Sink::insert(sink).exec(&txn).await?; } - StreamingJob::Subscription(subscription) => { - let job_id = Self::create_streaming_job_obj( - &txn, - ObjectType::Subscription, - subscription.owner as _, - Some(subscription.database_id as _), - Some(subscription.schema_id as _), - create_type, - ctx, - streaming_parallelism, - ) - .await?; - subscription.id = job_id as _; - let subscription: subscription::ActiveModel = subscription.clone().into(); - subscription.insert(&txn).await?; - } StreamingJob::Table(src, table, _) => { let job_id = Self::create_streaming_job_obj( &txn, @@ -270,12 +288,17 @@ impl CatalogController { } } + // get dependent secret ref. + let dependent_secret_refs = streaming_job.dependent_secret_refs()?; + + let dependent_objs = dependent_relations + .iter() + .chain(dependent_secret_refs.iter()); // record object dependency. - let dependent_relations = streaming_job.dependent_relations(); - if !dependent_relations.is_empty() { - ObjectDependency::insert_many(dependent_relations.into_iter().map(|id| { + if !dependent_secret_refs.is_empty() || !dependent_relations.is_empty() { + ObjectDependency::insert_many(dependent_objs.map(|id| { object_dependency::ActiveModel { - oid: Set(id as _), + oid: Set(*id as _), used_by: Set(streaming_job.id() as _), ..Default::default() } @@ -394,7 +417,7 @@ impl CatalogController { &self, job_id: ObjectId, is_cancelled: bool, - ) -> MetaResult<(bool, Vec)> { + ) -> MetaResult { let inner = self.inner.write().await; let txn = inner.db.begin().await?; @@ -404,7 +427,7 @@ impl CatalogController { id = job_id, "streaming job not found when aborting creating, might be cleaned by recovery" ); - return Ok((true, Vec::new())); + return Ok(true); } if !is_cancelled { @@ -419,7 +442,7 @@ impl CatalogController { id = job_id, "streaming job is created in background and still in creating status" ); - return Ok((false, Vec::new())); + return Ok(false); } } } @@ -432,13 +455,6 @@ impl CatalogController { .all(&txn) .await?; - let mv_table_id: Option = Table::find_by_id(job_id) - .select_only() - .column(table::Column::TableId) - .into_tuple() - .one(&txn) - .await?; - let associated_source_id: Option = Table::find_by_id(job_id) .select_only() .column(table::Column::OptionalAssociatedSourceId) @@ -459,11 +475,7 @@ impl CatalogController { } txn.commit().await?; - let mut state_table_ids = internal_table_ids; - - state_table_ids.extend(mv_table_id.into_iter()); - - Ok((true, state_table_ids)) + Ok(true) } pub async fn post_collect_table_fragments( @@ -556,12 +568,33 @@ impl CatalogController { return Err(MetaError::permission_denied("table version is stale")); } + // 2. check concurrent replace. + let referring_cnt = ObjectDependency::find() + .join( + JoinType::InnerJoin, + object_dependency::Relation::Object1.def(), + ) + .join(JoinType::InnerJoin, object::Relation::StreamingJob.def()) + .filter( + object_dependency::Column::Oid + .eq(id as ObjectId) + .and(object::Column::ObjType.eq(ObjectType::Table)) + .and(streaming_job::Column::JobStatus.ne(JobStatus::Created)), + ) + .count(&txn) + .await?; + if referring_cnt != 0 { + return Err(MetaError::permission_denied( + "table is being altered or referenced by some creating jobs", + )); + } + let parallelism = match specified_parallelism { None => StreamingParallelism::Adaptive, Some(n) => StreamingParallelism::Fixed(n.get() as _), }; - // 2. create streaming object for new replace table. + // 3. create streaming object for new replace table. let obj_id = Self::create_streaming_job_obj( &txn, ObjectType::Table, @@ -574,7 +607,7 @@ impl CatalogController { ) .await?; - // 3. record dependency for new replace table. + // 4. record dependency for new replace table. ObjectDependency::insert(object_dependency::ActiveModel { oid: Set(id as _), used_by: Set(obj_id as _), @@ -588,6 +621,186 @@ impl CatalogController { Ok(obj_id) } + /// `finish_streaming_job` marks job related objects as `Created` and notify frontend. + pub async fn finish_streaming_job( + &self, + job_id: ObjectId, + replace_table_job_info: Option<(crate::manager::StreamingJob, Vec, u32)>, + ) -> MetaResult { + let inner = self.inner.write().await; + let txn = inner.db.begin().await?; + + let job_type = Object::find_by_id(job_id) + .select_only() + .column(object::Column::ObjType) + .into_tuple() + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("streaming job", job_id))?; + + // update `created_at` as now() and `created_at_cluster_version` as current cluster version. + let res = Object::update_many() + .col_expr(object::Column::CreatedAt, Expr::current_timestamp().into()) + .col_expr( + object::Column::CreatedAtClusterVersion, + current_cluster_version().into(), + ) + .filter(object::Column::Oid.eq(job_id)) + .exec(&txn) + .await?; + if res.rows_affected == 0 { + return Err(MetaError::catalog_id_not_found("streaming job", job_id)); + } + + // mark the target stream job as `Created`. + let job = streaming_job::ActiveModel { + job_id: Set(job_id), + job_status: Set(JobStatus::Created), + ..Default::default() + }; + job.update(&txn).await?; + + // notify frontend: job, internal tables. + let internal_table_objs = Table::find() + .find_also_related(Object) + .filter(table::Column::BelongsToJobId.eq(job_id)) + .all(&txn) + .await?; + let mut relations = internal_table_objs + .iter() + .map(|(table, obj)| PbRelation { + relation_info: Some(PbRelationInfo::Table( + ObjectModel(table.clone(), obj.clone().unwrap()).into(), + )), + }) + .collect_vec(); + + match job_type { + ObjectType::Table => { + let (table, obj) = Table::find_by_id(job_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("table", job_id))?; + if let Some(source_id) = table.optional_associated_source_id { + let (src, obj) = Source::find_by_id(source_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("source", source_id))?; + relations.push(PbRelation { + relation_info: Some(PbRelationInfo::Source( + ObjectModel(src, obj.unwrap()).into(), + )), + }); + } + relations.push(PbRelation { + relation_info: Some(PbRelationInfo::Table( + ObjectModel(table, obj.unwrap()).into(), + )), + }); + } + ObjectType::Sink => { + let (sink, obj) = Sink::find_by_id(job_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("sink", job_id))?; + relations.push(PbRelation { + relation_info: Some(PbRelationInfo::Sink( + ObjectModel(sink, obj.unwrap()).into(), + )), + }); + } + ObjectType::Index => { + let (index, obj) = Index::find_by_id(job_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("index", job_id))?; + { + let (table, obj) = Table::find_by_id(index.index_table_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| { + MetaError::catalog_id_not_found("table", index.index_table_id) + })?; + relations.push(PbRelation { + relation_info: Some(PbRelationInfo::Table( + ObjectModel(table, obj.unwrap()).into(), + )), + }); + } + relations.push(PbRelation { + relation_info: Some(PbRelationInfo::Index( + ObjectModel(index, obj.unwrap()).into(), + )), + }); + } + ObjectType::Source => { + let (source, obj) = Source::find_by_id(job_id) + .find_also_related(Object) + .one(&txn) + .await? + .ok_or_else(|| MetaError::catalog_id_not_found("source", job_id))?; + relations.push(PbRelation { + relation_info: Some(PbRelationInfo::Source( + ObjectModel(source, obj.unwrap()).into(), + )), + }); + } + _ => unreachable!("invalid job type: {:?}", job_type), + } + + let fragment_mapping = get_fragment_mappings(&txn, job_id).await?; + + let replace_table_mapping_update = match replace_table_job_info { + Some((streaming_job, merge_updates, dummy_id)) => { + let incoming_sink_id = job_id; + + let (relations, fragment_mapping) = Self::finish_replace_streaming_job_inner( + dummy_id as ObjectId, + merge_updates, + None, + Some(incoming_sink_id as _), + None, + &txn, + streaming_job, + ) + .await?; + + Some((relations, fragment_mapping)) + } + None => None, + }; + + txn.commit().await?; + + self.notify_fragment_mapping(NotificationOperation::Add, fragment_mapping) + .await; + + let mut version = self + .notify_frontend( + NotificationOperation::Add, + NotificationInfo::RelationGroup(PbRelationGroup { relations }), + ) + .await; + + if let Some((relations, fragment_mapping)) = replace_table_mapping_update { + self.notify_fragment_mapping(NotificationOperation::Add, fragment_mapping) + .await; + version = self + .notify_frontend( + NotificationOperation::Update, + NotificationInfo::RelationGroup(PbRelationGroup { relations }), + ) + .await; + } + + Ok(version) + } + pub async fn finish_replace_streaming_job( &self, dummy_id: ObjectId, @@ -597,13 +810,53 @@ impl CatalogController { creating_sink_id: Option, dropping_sink_id: Option, ) -> MetaResult { + let inner = self.inner.write().await; + let txn = inner.db.begin().await?; + + let (relations, fragment_mapping) = Self::finish_replace_streaming_job_inner( + dummy_id, + merge_updates, + table_col_index_mapping, + creating_sink_id, + dropping_sink_id, + &txn, + streaming_job, + ) + .await?; + + txn.commit().await?; + + // FIXME: Do not notify frontend currently, because frontend nodes might refer to old table + // catalog and need to access the old fragment. Let frontend nodes delete the old fragment + // when they receive table catalog change. + // self.notify_fragment_mapping(NotificationOperation::Delete, old_fragment_mappings) + // .await; + self.notify_fragment_mapping(NotificationOperation::Add, fragment_mapping) + .await; + let version = self + .notify_frontend( + NotificationOperation::Update, + NotificationInfo::RelationGroup(PbRelationGroup { relations }), + ) + .await; + + Ok(version) + } + + pub async fn finish_replace_streaming_job_inner( + dummy_id: ObjectId, + merge_updates: Vec, + table_col_index_mapping: Option, + creating_sink_id: Option, + dropping_sink_id: Option, + txn: &DatabaseTransaction, + streaming_job: StreamingJob, + ) -> MetaResult<(Vec, Vec)> { // Question: The source catalog should be remain unchanged? let StreamingJob::Table(_, table, ..) = streaming_job else { unreachable!("unexpected job: {streaming_job:?}") }; - let inner = self.inner.write().await; - let txn = inner.db.begin().await?; let job_id = table.id as ObjectId; let mut table = table::ActiveModel::from(table); @@ -621,7 +874,7 @@ impl CatalogController { } table.incoming_sinks = Set(incoming_sinks.into()); - let table = table.update(&txn).await?; + let table = table.update(txn).await?; // Update state table fragment id. let fragment_table_ids: Vec<(FragmentId, I32Array)> = Fragment::find() @@ -632,7 +885,7 @@ impl CatalogController { ]) .filter(fragment::Column::JobId.eq(dummy_id)) .into_tuple() - .all(&txn) + .all(txn) .await?; for (fragment_id, state_table_ids) in fragment_table_ids { for state_table_id in state_table_ids.into_inner() { @@ -641,7 +894,7 @@ impl CatalogController { fragment_id: Set(Some(fragment_id)), ..Default::default() } - .update(&txn) + .update(txn) .await?; } } @@ -650,12 +903,12 @@ impl CatalogController { // 1. replace old fragments/actors with new ones. Fragment::delete_many() .filter(fragment::Column::JobId.eq(job_id)) - .exec(&txn) + .exec(txn) .await?; Fragment::update_many() .col_expr(fragment::Column::JobId, SimpleExpr::from(job_id)) .filter(fragment::Column::JobId.eq(dummy_id)) - .exec(&txn) + .exec(txn) .await?; // 2. update merges. @@ -686,7 +939,7 @@ impl CatalogController { actor::Column::UpstreamActorIds, ]) .into_tuple::<(ActorId, FragmentId, ActorUpstreamActors)>() - .one(&txn) + .one(txn) .await? .ok_or_else(|| { MetaError::catalog_id_not_found("actor", merge_update.actor_id) @@ -709,7 +962,7 @@ impl CatalogController { upstream_actor_ids: Set(upstream_actors), ..Default::default() } - .update(&txn) + .update(txn) .await?; to_update_fragment_ids.insert(fragment_id); @@ -724,7 +977,7 @@ impl CatalogController { fragment::Column::UpstreamFragmentId, ]) .into_tuple::<(FragmentId, StreamNode, I32Array)>() - .one(&txn) + .one(txn) .await? .map(|(id, node, upstream)| (id, node.to_protobuf(), upstream)) .ok_or_else(|| MetaError::catalog_id_not_found("fragment", fragment_id))?; @@ -748,18 +1001,18 @@ impl CatalogController { upstream_fragment_id: Set(upstream_fragment_id), ..Default::default() } - .update(&txn) + .update(txn) .await?; } // 3. remove dummy object. - Object::delete_by_id(dummy_id).exec(&txn).await?; + Object::delete_by_id(dummy_id).exec(txn).await?; // 4. update catalogs and notify. let mut relations = vec![]; let table_obj = table .find_related(Object) - .one(&txn) + .one(txn) .await? .ok_or_else(|| MetaError::catalog_id_not_found("object", table.table_id))?; relations.push(PbRelation { @@ -775,7 +1028,7 @@ impl CatalogController { .columns([index::Column::IndexId, index::Column::IndexItems]) .filter(index::Column::PrimaryTableId.eq(job_id)) .into_tuple() - .all(&txn) + .all(txn) .await?; for (index_id, nodes) in index_items { let mut pb_nodes = nodes.to_protobuf(); @@ -787,11 +1040,11 @@ impl CatalogController { index_items: Set(pb_nodes.into()), ..Default::default() } - .update(&txn) + .update(txn) .await?; let index_obj = index .find_related(Object) - .one(&txn) + .one(txn) .await? .ok_or_else(|| MetaError::catalog_id_not_found("object", index.index_id))?; relations.push(PbRelation { @@ -801,25 +1054,10 @@ impl CatalogController { }); } } - let fragment_mapping = get_fragment_mappings(&txn, job_id).await?; - txn.commit().await?; + let fragment_mapping: Vec<_> = get_fragment_mappings(txn, job_id as _).await?; - // FIXME: Do not notify frontend currently, because frontend nodes might refer to old table - // catalog and need to access the old fragment. Let frontend nodes delete the old fragment - // when they receive table catalog change. - // self.notify_fragment_mapping(NotificationOperation::Delete, old_fragment_mappings) - // .await; - self.notify_fragment_mapping(NotificationOperation::Add, fragment_mapping) - .await; - let version = self - .notify_frontend( - NotificationOperation::Update, - NotificationInfo::RelationGroup(PbRelationGroup { relations }), - ) - .await; - - Ok(version) + Ok((relations, fragment_mapping)) } /// `try_abort_replacing_streaming_job` is used to abort the replacing streaming job, the input `job_id` is the dummy job id. @@ -1033,6 +1271,8 @@ impl CatalogController { let txn = inner.db.begin().await?; + let parallel_unit_to_worker = get_parallel_unit_to_worker_map(&txn).await?; + let mut fragment_mapping_to_notify = vec![]; // for assert only @@ -1062,10 +1302,6 @@ impl CatalogController { .exec(&txn) .await?; - // newly created actor - let mut new_actors = vec![]; - let mut new_actor_dispatchers = vec![]; - for ( PbStreamActor { actor_id, @@ -1085,6 +1321,7 @@ impl CatalogController { ) in newly_created_actors { let mut actor_upstreams = BTreeMap::>::new(); + let mut new_actor_dispatchers = vec![]; if let Some(nodes) = &mut nodes { visit_stream_node(nodes, |node| { @@ -1123,7 +1360,7 @@ impl CatalogController { .get(&actor_id) .map(|splits| splits.iter().map(PbConnectorSplit::from).collect_vec()); - new_actors.push(actor::ActiveModel { + Actor::insert(actor::ActiveModel { actor_id: Set(actor_id as _), fragment_id: Set(fragment_id as _), status: Set(ActorStatus::Running), @@ -1133,7 +1370,9 @@ impl CatalogController { upstream_actor_ids: Set(actor_upstreams), vnode_bitmap: Set(vnode_bitmap.as_ref().map(|bitmap| bitmap.into())), expr_context: Set(expr_context.as_ref().unwrap().into()), - }); + }) + .exec(&txn) + .await?; for PbDispatcher { r#type: dispatcher_type, @@ -1157,16 +1396,11 @@ impl CatalogController { downstream_actor_ids: Set(downstream_actor_id.into()), }) } - } - - if !new_actors.is_empty() { - Actor::insert_many(new_actors).exec(&txn).await?; - } - - if !new_actor_dispatchers.is_empty() { - ActorDispatcher::insert_many(new_actor_dispatchers) - .exec(&txn) - .await?; + if !new_actor_dispatchers.is_empty() { + ActorDispatcher::insert_many(new_actor_dispatchers) + .exec(&txn) + .await?; + } } // actor update @@ -1214,9 +1448,13 @@ impl CatalogController { fragment.vnode_mapping = Set((&vnode_mapping).into()); fragment.update(&txn).await?; - fragment_mapping_to_notify.push(FragmentParallelUnitMapping { + let worker_slot_mapping = ParallelUnitMapping::from_protobuf(&vnode_mapping) + .to_worker_slot(¶llel_unit_to_worker)? + .to_protobuf(); + + fragment_mapping_to_notify.push(FragmentWorkerSlotMapping { fragment_id: fragment_id as u32, - mapping: Some(vnode_mapping), + mapping: Some(worker_slot_mapping), }); // for downstream and upstream diff --git a/src/meta/src/controller/utils.rs b/src/meta/src/controller/utils.rs index 173634ab03cc7..8a8f1b2d71cda 100644 --- a/src/meta/src/controller/utils.rs +++ b/src/meta/src/controller/utils.rs @@ -23,12 +23,12 @@ use risingwave_meta_model_v2::object::ObjectType; use risingwave_meta_model_v2::prelude::*; use risingwave_meta_model_v2::{ actor, actor_dispatcher, connection, database, fragment, function, index, object, - object_dependency, schema, sink, source, table, user, user_privilege, view, ActorId, - DataTypeArray, DatabaseId, FragmentId, FragmentVnodeMapping, I32Array, ObjectId, PrivilegeId, - SchemaId, SourceId, StreamNode, UserId, + object_dependency, schema, secret, sink, source, subscription, table, user, user_privilege, + view, worker_property, ActorId, DataTypeArray, DatabaseId, FragmentId, FragmentVnodeMapping, + I32Array, ObjectId, PrivilegeId, SchemaId, SourceId, StreamNode, UserId, WorkerId, }; -use risingwave_pb::catalog::{PbConnection, PbFunction}; -use risingwave_pb::meta::PbFragmentParallelUnitMapping; +use risingwave_pb::catalog::{PbConnection, PbFunction, PbSecret, PbSubscription}; +use risingwave_pb::meta::{PbFragmentParallelUnitMapping, PbFragmentWorkerSlotMapping}; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::{PbFragmentTypeFlag, PbStreamNode, StreamSource}; use risingwave_pb::user::grant_privilege::{PbAction, PbActionWithGrantOption, PbObject}; @@ -42,6 +42,7 @@ use sea_orm::{ Order, PaginatorTrait, QueryFilter, QuerySelect, RelationTrait, Statement, }; +use crate::controller::catalog::CatalogController; use crate::{MetaError, MetaResult}; /// This function will construct a query using recursive cte to find all objects[(id, `obj_type`)] that are used by the given object. @@ -410,6 +411,54 @@ where Ok(()) } +pub async fn check_secret_name_duplicate(pb_secret: &PbSecret, db: &C) -> MetaResult<()> +where + C: ConnectionTrait, +{ + let count = Secret::find() + .inner_join(Object) + .filter( + object::Column::DatabaseId + .eq(pb_secret.database_id as DatabaseId) + .and(object::Column::SchemaId.eq(pb_secret.schema_id as SchemaId)) + .and(secret::Column::Name.eq(&pb_secret.name)), + ) + .count(db) + .await?; + if count > 0 { + assert_eq!(count, 1); + return Err(MetaError::catalog_duplicated("secret", &pb_secret.name)); + } + Ok(()) +} + +pub async fn check_subscription_name_duplicate( + pb_subscription: &PbSubscription, + db: &C, +) -> MetaResult<()> +where + C: ConnectionTrait, +{ + let count = Subscription::find() + .inner_join(Object) + .filter( + object::Column::DatabaseId + .eq(pb_subscription.database_id as DatabaseId) + .and(object::Column::SchemaId.eq(pb_subscription.schema_id as SchemaId)) + .and(subscription::Column::Name.eq(&pb_subscription.name)), + ) + .count(db) + .await?; + if count > 0 { + assert_eq!(count, 1); + return Err(MetaError::catalog_duplicated( + "subscription", + &pb_subscription.name, + )); + } + Ok(()) +} + /// `check_user_name_duplicate` checks whether the user is already existed in the cluster. pub async fn check_user_name_duplicate(name: &str, db: &C) -> MetaResult<()> where @@ -727,14 +776,14 @@ where let obj = match object.obj_type { ObjectType::Database => PbObject::DatabaseId(oid), ObjectType::Schema => PbObject::SchemaId(oid), - ObjectType::Table => PbObject::TableId(oid), + ObjectType::Table | ObjectType::Index => PbObject::TableId(oid), ObjectType::Source => PbObject::SourceId(oid), ObjectType::Sink => PbObject::SinkId(oid), ObjectType::View => PbObject::ViewId(oid), ObjectType::Function => PbObject::FunctionId(oid), - ObjectType::Index => unreachable!("index is not supported yet"), ObjectType::Connection => unreachable!("connection is not supported yet"), ObjectType::Subscription => PbObject::SubscriptionId(oid), + ObjectType::Secret => unreachable!("secret is not supported yet"), }; PbGrantPrivilege { action_with_opts: vec![PbActionWithGrantOption { @@ -789,10 +838,12 @@ where pub async fn get_fragment_mappings( db: &C, job_id: ObjectId, -) -> MetaResult> +) -> MetaResult> where C: ConnectionTrait, { + let parallel_unit_to_worker = get_parallel_unit_to_worker_map(db).await?; + let fragment_mappings: Vec<(FragmentId, FragmentVnodeMapping)> = Fragment::find() .select_only() .columns([fragment::Column::FragmentId, fragment::Column::VnodeMapping]) @@ -801,13 +852,7 @@ where .all(db) .await?; - Ok(fragment_mappings - .into_iter() - .map(|(fragment_id, mapping)| PbFragmentParallelUnitMapping { - fragment_id: fragment_id as _, - mapping: Some(mapping.to_protobuf()), - }) - .collect()) + CatalogController::convert_fragment_mappings(fragment_mappings, ¶llel_unit_to_worker) } /// `get_fragment_mappings_by_jobs` returns the fragment vnode mappings of the given job list. @@ -879,15 +924,19 @@ pub fn find_stream_source(stream_node: &PbStreamNode) -> Option<&StreamSource> { pub async fn resolve_source_register_info_for_jobs( db: &C, streaming_jobs: Vec, -) -> MetaResult<(HashMap>, HashSet)> +) -> MetaResult<( + HashMap>, + HashSet, + HashSet, +)> where C: ConnectionTrait, { if streaming_jobs.is_empty() { - return Ok((HashMap::default(), HashSet::default())); + return Ok((HashMap::default(), HashSet::default(), HashSet::default())); } - let mut fragments: Vec<(FragmentId, i32, StreamNode)> = Fragment::find() + let fragments: Vec<(FragmentId, i32, StreamNode)> = Fragment::find() .select_only() .columns([ fragment::Column::FragmentId, @@ -908,10 +957,16 @@ where .all(db) .await?; - fragments.retain(|(_, mask, _)| *mask & PbFragmentTypeFlag::Source as i32 != 0); + let removed_fragments = fragments + .iter() + .map(|(fragment_id, _, _)| *fragment_id) + .collect(); let mut source_fragment_ids = HashMap::new(); - for (fragment_id, _, stream_node) in fragments { + for (fragment_id, mask, stream_node) in fragments { + if mask & PbFragmentTypeFlag::Source as i32 == 0 { + continue; + } if let Some(source) = find_stream_source(&stream_node.to_protobuf()) { source_fragment_ids .entry(source.source_id as SourceId) @@ -920,5 +975,36 @@ where } } - Ok((source_fragment_ids, actors.into_iter().collect())) + Ok(( + source_fragment_ids, + actors.into_iter().collect(), + removed_fragments, + )) +} + +pub(crate) async fn get_parallel_unit_to_worker_map(db: &C) -> MetaResult> +where + C: ConnectionTrait, +{ + let worker_parallel_units = WorkerProperty::find() + .select_only() + .columns([ + worker_property::Column::WorkerId, + worker_property::Column::ParallelUnitIds, + ]) + .into_tuple::<(WorkerId, I32Array)>() + .all(db) + .await?; + + let parallel_unit_to_worker = worker_parallel_units + .into_iter() + .flat_map(|(worker_id, parallel_unit_ids)| { + parallel_unit_ids + .into_inner() + .into_iter() + .map(move |parallel_unit_id| (parallel_unit_id as u32, worker_id as u32)) + }) + .collect::>(); + + Ok(parallel_unit_to_worker) } diff --git a/src/meta/src/dashboard/mod.rs b/src/meta/src/dashboard/mod.rs index 6d05666819437..1229554032614 100644 --- a/src/meta/src/dashboard/mod.rs +++ b/src/meta/src/dashboard/mod.rs @@ -24,6 +24,7 @@ use axum::http::{Method, StatusCode}; use axum::response::{IntoResponse, Response}; use axum::routing::get; use axum::Router; +use risingwave_common::util::StackTraceResponseExt; use risingwave_rpc_client::ComputeClientPool; use tokio::net::TcpListener; use tower::ServiceBuilder; @@ -54,7 +55,7 @@ pub(super) mod handlers { use itertools::Itertools; use risingwave_common_heap_profiling::COLLAPSED_SUFFIX; use risingwave_pb::catalog::table::TableType; - use risingwave_pb::catalog::{Sink, Source, Table, View}; + use risingwave_pb::catalog::{PbDatabase, PbSchema, Sink, Source, Subscription, Table, View}; use risingwave_pb::common::{WorkerNode, WorkerType}; use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies; use risingwave_pb::meta::PbTableFragments; @@ -62,6 +63,7 @@ pub(super) mod handlers { GetBackPressureResponse, HeapProfilingResponse, ListHeapProfilingResponse, StackTraceResponse, }; + use risingwave_pb::user::PbUserInfo; use serde_json::json; use thiserror_ext::AsReport; @@ -139,6 +141,21 @@ pub(super) mod handlers { list_table_catalogs_inner(&srv.metadata_manager, TableType::Index).await } + pub async fn list_subscription( + Extension(srv): Extension, + ) -> Result>> { + let subscriptions = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_subscriptions().await, + MetadataManager::V2(mgr) => mgr + .catalog_controller + .list_subscriptions() + .await + .map_err(err)?, + }; + + Ok(Json(subscriptions)) + } + pub async fn list_internal_tables( Extension(srv): Extension, ) -> Result>> { @@ -194,6 +211,37 @@ pub(super) mod handlers { Ok(Json(table_fragments)) } + pub async fn list_users(Extension(srv): Extension) -> Result>> { + let users = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_users().await, + MetadataManager::V2(mgr) => mgr.catalog_controller.list_users().await.map_err(err)?, + }; + + Ok(Json(users)) + } + + pub async fn list_databases( + Extension(srv): Extension, + ) -> Result>> { + let databases = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_databases().await, + MetadataManager::V2(mgr) => { + mgr.catalog_controller.list_databases().await.map_err(err)? + } + }; + + Ok(Json(databases)) + } + + pub async fn list_schemas(Extension(srv): Extension) -> Result>> { + let schemas = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_schemas().await, + MetadataManager::V2(mgr) => mgr.catalog_controller.list_schemas().await.map_err(err)?, + }; + + Ok(Json(schemas)) + } + pub async fn list_object_dependencies( Extension(srv): Extension, ) -> Result>> { @@ -213,20 +261,13 @@ pub(super) mod handlers { worker_nodes: impl IntoIterator, compute_clients: &ComputeClientPool, ) -> Result> { - let mut all = Default::default(); - - fn merge(a: &mut StackTraceResponse, b: StackTraceResponse) { - a.actor_traces.extend(b.actor_traces); - a.rpc_traces.extend(b.rpc_traces); - a.compaction_task_traces.extend(b.compaction_task_traces); - a.inflight_barrier_traces.extend(b.inflight_barrier_traces); - } + let mut all = StackTraceResponse::default(); for worker_node in worker_nodes { let client = compute_clients.get(worker_node).await.map_err(err)?; let result = client.stack_trace().await.map_err(err)?; - merge(&mut all, result); + all.merge_other(result); } Ok(all.into()) @@ -391,9 +432,13 @@ impl DashboardService { .route("/materialized_views", get(list_materialized_views)) .route("/tables", get(list_tables)) .route("/indexes", get(list_indexes)) + .route("/subscriptions", get(list_subscription)) .route("/internal_tables", get(list_internal_tables)) .route("/sources", get(list_sources)) .route("/sinks", get(list_sinks)) + .route("/users", get(list_users)) + .route("/databases", get(list_databases)) + .route("/schemas", get(list_schemas)) .route("/object_dependencies", get(list_object_dependencies)) .route("/metrics/cluster", get(prometheus::list_prometheus_cluster)) .route( diff --git a/src/meta/src/error.rs b/src/meta/src/error.rs index 8aeaed2f9c5a8..9d3d558ac4839 100644 --- a/src/meta/src/error.rs +++ b/src/meta/src/error.rs @@ -13,6 +13,7 @@ // limitations under the License. use risingwave_common::error::BoxedError; +use risingwave_common::hash::ParallelUnitError; use risingwave_common::session_config::SessionConfigError; use risingwave_connector::error::ConnectorError; use risingwave_connector::sink::SinkError; @@ -125,6 +126,13 @@ pub enum MetaErrorInner { // Indicates that recovery was triggered manually. #[error("adhoc recovery triggered")] AdhocRecovery, + + #[error("ParallelUnit error: {0}")] + ParallelUnit( + #[from] + #[backtrace] + ParallelUnitError, + ), } impl MetaError { diff --git a/src/meta/src/hummock/compaction/compaction_config.rs b/src/meta/src/hummock/compaction/compaction_config.rs index 6048e301c97d6..de91bf4f79ded 100644 --- a/src/meta/src/hummock/compaction/compaction_config.rs +++ b/src/meta/src/hummock/compaction/compaction_config.rs @@ -17,8 +17,6 @@ use risingwave_common::config::CompactionConfig as CompactionConfigOpt; use risingwave_pb::hummock::compaction_config::CompactionMode; use risingwave_pb::hummock::CompactionConfig; -const MAX_LEVEL: u64 = 6; - pub struct CompactionConfigBuilder { config: CompactionConfig, } @@ -29,7 +27,7 @@ impl CompactionConfigBuilder { config: CompactionConfig { max_bytes_for_level_base: compaction_config::max_bytes_for_level_base(), max_bytes_for_level_multiplier: compaction_config::max_bytes_for_level_multiplier(), - max_level: MAX_LEVEL, + max_level: compaction_config::max_level() as u64, max_compaction_bytes: compaction_config::max_compaction_bytes(), sub_level_max_compaction_bytes: compaction_config::sub_level_max_compaction_bytes(), level0_tier_compact_file_number: compaction_config::level0_tier_compact_file_number( @@ -66,6 +64,7 @@ impl CompactionConfigBuilder { compaction_config::level0_overlapping_sub_level_compact_level_count(), tombstone_reclaim_ratio: compaction_config::tombstone_reclaim_ratio(), enable_emergency_picker: compaction_config::enable_emergency_picker(), + max_l0_compact_level_count: compaction_config::max_l0_compact_level_count(), }, } } @@ -94,6 +93,7 @@ impl CompactionConfigBuilder { .max_space_reclaim_bytes(opt.max_space_reclaim_bytes) .level0_max_compact_file_number(opt.level0_max_compact_file_number) .tombstone_reclaim_ratio(opt.tombstone_reclaim_ratio) + .max_level(opt.max_level as u64) } pub fn build(self) -> CompactionConfig { diff --git a/src/meta/src/hummock/compaction/mod.rs b/src/meta/src/hummock/compaction/mod.rs index 49eeaa5778ab7..bf4b608fe59ad 100644 --- a/src/meta/src/hummock/compaction/mod.rs +++ b/src/meta/src/hummock/compaction/mod.rs @@ -16,12 +16,12 @@ pub mod compaction_config; mod overlap_strategy; -use risingwave_common::catalog::TableOption; +use risingwave_common::catalog::{TableId, TableOption}; use risingwave_pb::hummock::compact_task::{self, TaskType}; mod picker; pub mod selector; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::{Debug, Formatter}; use std::sync::Arc; @@ -92,6 +92,7 @@ impl CompactStatus { pub fn get_compact_task( &mut self, levels: &Levels, + member_table_ids: &BTreeSet, task_id: HummockCompactionTaskId, group: &CompactionGroup, stats: &mut LocalSelectorStatistic, @@ -106,6 +107,7 @@ impl CompactStatus { task_id, group, levels, + member_table_ids, &mut self.level_handlers, stats, table_id_to_options.clone(), @@ -121,6 +123,7 @@ impl CompactStatus { task_id, group, levels, + member_table_ids, &mut self.level_handlers, stats, table_id_to_options, @@ -193,12 +196,6 @@ pub fn create_compaction_task( ) -> CompactionTask { let target_file_size = if input.target_level == 0 { compaction_config.target_file_size_base - } else if input.target_level == base_level { - // This is just a temporary optimization measure. We hope to reduce the size of SST as much - // as possible to reduce the amount of data blocked by a single task during compaction, - // but too many files will increase computing overhead. - // TODO: remove it after can reduce configuration `target_file_size_base`. - compaction_config.target_file_size_base / 4 } else { assert!(input.target_level >= base_level); let step = (input.target_level - base_level) / 2; diff --git a/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs index 6617b9496fe10..bf05afc6c6e88 100644 --- a/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::cell::RefCell; use std::sync::Arc; use itertools::Itertools; @@ -28,6 +29,10 @@ use crate::hummock::compaction::picker::TrivialMovePicker; use crate::hummock::compaction::{create_overlap_strategy, CompactionDeveloperConfig}; use crate::hummock::level_handler::LevelHandler; +std::thread_local! { + static LOG_COUNTER: RefCell = const { RefCell::new(0) }; +} + pub struct LevelCompactionPicker { target_level: usize, config: Arc, @@ -161,6 +166,7 @@ impl LevelCompactionPicker { self.config.level0_max_compact_file_number, overlap_strategy.clone(), self.developer_config.enable_check_task_level_overlap, + self.config.max_l0_compact_level_count as usize, ); let mut max_vnode_partition_idx = 0; @@ -253,13 +259,21 @@ impl LevelCompactionPicker { stats, ) { if l0.total_file_size > target_level.total_file_size * 8 { - tracing::warn!("skip task with level count: {}, file count: {}, select size: {}, target size: {}, target level size: {}", - result.input_levels.len(), - result.total_file_count, - result.select_input_size, - result.target_input_size, - target_level.total_file_size, - ); + let log_counter = LOG_COUNTER.with_borrow_mut(|counter| { + *counter += 1; + *counter + }); + + // reduce log + if log_counter % 100 == 0 { + tracing::warn!("skip task with level count: {}, file count: {}, select size: {}, target size: {}, target level size: {}", + result.input_levels.len(), + result.total_file_count, + result.select_input_size, + result.target_input_size, + target_level.total_file_size, + ); + } } continue; } @@ -272,14 +286,11 @@ impl LevelCompactionPicker { #[cfg(test)] pub mod tests { - use itertools::Itertools; use super::*; use crate::hummock::compaction::compaction_config::CompactionConfigBuilder; use crate::hummock::compaction::selector::tests::*; - use crate::hummock::compaction::{ - CompactionDeveloperConfig, CompactionMode, TierCompactionPicker, - }; + use crate::hummock::compaction::{CompactionMode, TierCompactionPicker}; fn create_compaction_picker_for_test() -> LevelCompactionPicker { let config = Arc::new( @@ -315,7 +326,6 @@ pub mod tests { generate_table(1, 1, 201, 210, 1), ], )], - member_table_ids: vec![1], ..Default::default() }; let mut local_stats = LocalPickerStatistic::default(); @@ -408,7 +418,6 @@ pub mod tests { total_file_size: 0, uncompressed_file_size: 0, }), - member_table_ids: vec![1], ..Default::default() }; push_tables_level0_nonoverlapping(&mut levels, vec![generate_table(1, 1, 50, 140, 2)]); @@ -471,7 +480,6 @@ pub mod tests { total_file_size: 0, uncompressed_file_size: 0, }), - member_table_ids: vec![1], ..Default::default() }; push_tables_level0_nonoverlapping( @@ -575,7 +583,6 @@ pub mod tests { let levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(3, 1, 0, 100000, 1)])], - member_table_ids: vec![1], ..Default::default() }; let levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; @@ -653,7 +660,6 @@ pub mod tests { let levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(3, 1, 0, 100000, 1)])], - member_table_ids: vec![1], ..Default::default() }; let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; @@ -720,7 +726,6 @@ pub mod tests { let levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(3, 1, 1, 100, 1)])], - member_table_ids: vec![1], ..Default::default() }; let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; diff --git a/src/meta/src/hummock/compaction/picker/compaction_task_validator.rs b/src/meta/src/hummock/compaction/picker/compaction_task_validator.rs index 29119ae283b0a..c7dd27a6b1907 100644 --- a/src/meta/src/hummock/compaction/picker/compaction_task_validator.rs +++ b/src/meta/src/hummock/compaction/picker/compaction_task_validator.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use risingwave_pb::hummock::CompactionConfig; -use super::{CompactionInput, LocalPickerStatistic, MAX_COMPACT_LEVEL_COUNT}; +use super::{CompactionInput, LocalPickerStatistic}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ValidationRuleType { @@ -89,14 +89,8 @@ struct TierCompactionTaskValidationRule { impl CompactionTaskValidationRule for TierCompactionTaskValidationRule { fn validate(&self, input: &CompactionInput, stats: &mut LocalPickerStatistic) -> bool { - // Limit sstable file count to avoid using too much memory. - let overlapping_max_compact_file_numer = std::cmp::min( - self.config.level0_max_compact_file_number, - MAX_COMPACT_LEVEL_COUNT as u64, - ); - - if input.total_file_count >= overlapping_max_compact_file_numer - || input.input_levels.len() >= MAX_COMPACT_LEVEL_COUNT + if input.total_file_count >= self.config.level0_max_compact_file_number + || input.input_levels.len() >= self.config.max_l0_compact_level_count as usize { return true; } @@ -130,7 +124,7 @@ impl CompactionTaskValidationRule for IntraCompactionTaskValidationRule { fn validate(&self, input: &CompactionInput, stats: &mut LocalPickerStatistic) -> bool { if (input.total_file_count >= self.config.level0_max_compact_file_number && input.input_levels.len() > 1) - || input.input_levels.len() >= MAX_COMPACT_LEVEL_COUNT + || input.input_levels.len() >= self.config.max_l0_compact_level_count as usize { return true; } @@ -178,7 +172,7 @@ struct BaseCompactionTaskValidationRule { impl CompactionTaskValidationRule for BaseCompactionTaskValidationRule { fn validate(&self, input: &CompactionInput, stats: &mut LocalPickerStatistic) -> bool { if input.total_file_count >= self.config.level0_max_compact_file_number - || input.input_levels.len() >= MAX_COMPACT_LEVEL_COUNT + || input.input_levels.len() >= self.config.max_l0_compact_level_count as usize { return true; } diff --git a/src/meta/src/hummock/compaction/picker/intra_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/intra_compaction_picker.rs index a6c4344532d77..5cc65bd38a1c8 100644 --- a/src/meta/src/hummock/compaction/picker/intra_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/intra_compaction_picker.rs @@ -44,19 +44,9 @@ impl CompactionPicker for IntraCompactionPicker { if l0.sub_levels.is_empty() { return None; } - if l0.sub_levels[0].level_type != LevelType::Nonoverlapping as i32 - && l0.sub_levels[0].table_infos.len() > 1 - { - stats.skip_by_overlapping += 1; - return None; - } - - let is_l0_pending_compact = - level_handlers[0].is_level_all_pending_compact(&l0.sub_levels[0]); - if is_l0_pending_compact { - stats.skip_by_pending_files += 1; - return None; + if let Some(ret) = self.pick_l0_trivial_move_file(l0, level_handlers, stats) { + return Some(ret); } let vnode_partition_count = self.config.split_weight_by_vnode; @@ -67,18 +57,13 @@ impl CompactionPicker for IntraCompactionPicker { return Some(ret); } - if let Some(ret) = self.pick_l0_intra(l0, &level_handlers[0], vnode_partition_count, stats) - { - return Some(ret); - } - - self.pick_l0_trivial_move_file(l0, level_handlers, stats) + self.pick_l0_intra(l0, &level_handlers[0], vnode_partition_count, stats) } } impl IntraCompactionPicker { #[cfg(test)] - pub fn new( + pub fn for_test( config: Arc, developer_config: Arc, ) -> IntraCompactionPicker { @@ -94,6 +79,7 @@ impl IntraCompactionPicker { compaction_task_validator: Arc, developer_config: Arc, ) -> IntraCompactionPicker { + assert!(config.level0_sub_level_compact_level_count > 1); IntraCompactionPicker { config, compaction_task_validator, @@ -158,6 +144,7 @@ impl IntraCompactionPicker { self.config.level0_max_compact_file_number, overlap_strategy.clone(), self.developer_config.enable_check_task_level_overlap, + self.config.max_l0_compact_level_count as usize, ); let l0_select_tables_vec = non_overlap_sub_level_picker @@ -421,7 +408,7 @@ pub mod tests { .level0_sub_level_compact_level_count(1) .build(), ); - IntraCompactionPicker::new(config, Arc::new(CompactionDeveloperConfig::default())) + IntraCompactionPicker::for_test(config, Arc::new(CompactionDeveloperConfig::default())) } #[test] @@ -441,7 +428,6 @@ pub mod tests { total_file_size: 0, uncompressed_file_size: 0, }), - member_table_ids: vec![1], ..Default::default() }; push_tables_level0_nonoverlapping( @@ -486,7 +472,6 @@ pub mod tests { generate_table(1, 1, 100, 210, 2), generate_table(2, 1, 200, 250, 2), ])), - member_table_ids: vec![1], ..Default::default() }; let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; @@ -524,7 +509,6 @@ pub mod tests { let levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(100, 1, 0, 1000, 1)])], - member_table_ids: vec![1], ..Default::default() }; let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; @@ -535,8 +519,10 @@ pub mod tests { .level0_overlapping_sub_level_compact_level_count(4) .build(), ); - let mut picker = - IntraCompactionPicker::new(config, Arc::new(CompactionDeveloperConfig::default())); + let mut picker = IntraCompactionPicker::for_test( + config, + Arc::new(CompactionDeveloperConfig::default()), + ); let mut local_stats = LocalPickerStatistic::default(); let ret = picker .pick_compaction(&levels, &levels_handler, &mut local_stats) @@ -571,7 +557,6 @@ pub mod tests { let levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(100, 1, 0, 1000, 1)])], - member_table_ids: vec![1], ..Default::default() }; let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; @@ -581,8 +566,10 @@ pub mod tests { .level0_sub_level_compact_level_count(1) .build(), ); - let mut picker = - IntraCompactionPicker::new(config, Arc::new(CompactionDeveloperConfig::default())); + let mut picker = IntraCompactionPicker::for_test( + config, + Arc::new(CompactionDeveloperConfig::default()), + ); let mut local_stats = LocalPickerStatistic::default(); let ret = picker .pick_compaction(&levels, &levels_handler, &mut local_stats) @@ -641,7 +628,6 @@ pub mod tests { let levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(100, 1, 0, 1000, 1)])], - member_table_ids: vec![1], ..Default::default() }; let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; @@ -651,8 +637,10 @@ pub mod tests { .level0_sub_level_compact_level_count(1) .build(), ); - let mut picker = - IntraCompactionPicker::new(config, Arc::new(CompactionDeveloperConfig::default())); + let mut picker = IntraCompactionPicker::for_test( + config, + Arc::new(CompactionDeveloperConfig::default()), + ); let mut local_stats = LocalPickerStatistic::default(); let ret = picker .pick_compaction(&levels, &levels_handler, &mut local_stats) @@ -705,7 +693,7 @@ pub mod tests { .build(), ); let mut picker = - IntraCompactionPicker::new(config, Arc::new(CompactionDeveloperConfig::default())); + IntraCompactionPicker::for_test(config, Arc::new(CompactionDeveloperConfig::default())); // Cannot trivial move because there is only 1 sub-level. let l0 = generate_l0_overlapping_sublevels(vec![vec![ @@ -715,7 +703,6 @@ pub mod tests { let levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(100, 1, 0, 1000, 1)])], - member_table_ids: vec![1], ..Default::default() }; levels_handler[1].add_pending_task(100, 1, levels.levels[0].get_table_infos()); @@ -736,7 +723,6 @@ pub mod tests { let mut levels = Levels { l0: Some(l0), levels: vec![generate_level(1, vec![generate_table(100, 1, 0, 1000, 1)])], - member_table_ids: vec![1], ..Default::default() }; assert!(picker @@ -792,4 +778,71 @@ pub mod tests { .unwrap(); assert_eq!(ret.input_levels.len(), 2); } + + #[test] + fn test_priority() { + let config = Arc::new( + CompactionConfigBuilder::new() + .level0_max_compact_file_number(20) + .sub_level_max_compaction_bytes(1) + .level0_sub_level_compact_level_count(2) + .build(), + ); + let mut table_infos = vec![]; + for epoch in 1..3 { + let base = epoch * 100; + let mut ssts = vec![]; + for i in 1..50 { + let left = (i as usize) * 100; + let right = left + 100; + ssts.push(generate_table(base + i, 1, left, right, epoch)); + } + table_infos.push(ssts); + } + + let mut l0 = generate_l0_nonoverlapping_multi_sublevels(table_infos); + // trivial-move + l0.sub_levels[1] + .table_infos + .push(generate_table(9999, 900000000, 0, 100, 1)); + + l0.sub_levels[0].total_file_size = 1; + l0.sub_levels[1].total_file_size = 1; + + let mut picker = IntraCompactionPicker::new_with_validator( + config, + Arc::new(CompactionTaskValidator::unused()), + Arc::new(CompactionDeveloperConfig::default()), + ); + let mut levels_handler = vec![LevelHandler::new(0), LevelHandler::new(1)]; + let mut local_stats = LocalPickerStatistic::default(); + + let levels = Levels { + l0: Some(l0), + levels: vec![generate_level(1, vec![generate_table(100, 1, 0, 1000, 1)])], + ..Default::default() + }; + + let ret = picker.pick_compaction(&levels, &levels_handler, &mut local_stats); + assert!(is_l0_trivial_move(ret.as_ref().unwrap())); + ret.as_ref() + .unwrap() + .add_pending_task(1, &mut levels_handler); + let ret = picker.pick_compaction(&levels, &levels_handler, &mut local_stats); + assert!(ret.is_some()); + let input = ret.as_ref().unwrap(); + assert_eq!(input.input_levels.len(), 2); + assert_ne!( + levels.l0.as_ref().unwrap().get_sub_levels()[0] + .table_infos + .len(), + input.input_levels[0].table_infos.len() + ); + assert_ne!( + levels.l0.as_ref().unwrap().get_sub_levels()[1] + .table_infos + .len(), + input.input_levels[1].table_infos.len() + ); + } } diff --git a/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs index f46a99bc80c0d..2d1b2c95b20aa 100644 --- a/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs @@ -326,10 +326,10 @@ impl CompactionPicker for ManualCompactionPicker { #[cfg(test)] pub mod tests { - use std::collections::{HashMap, HashSet}; + use std::collections::{BTreeSet, HashMap}; use risingwave_pb::hummock::compact_task; - pub use risingwave_pb::hummock::{KeyRange, Level, LevelType}; + pub use risingwave_pb::hummock::KeyRange; use super::*; use crate::hummock::compaction::compaction_config::CompactionConfigBuilder; @@ -1198,6 +1198,7 @@ pub mod tests { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, HashMap::default(), @@ -1235,6 +1236,7 @@ pub mod tests { 2, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, HashMap::default(), @@ -1308,6 +1310,7 @@ pub mod tests { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, HashMap::default(), @@ -1347,6 +1350,7 @@ pub mod tests { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, HashMap::default(), diff --git a/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs index 01c3c050e574c..57dd5469d42ae 100644 --- a/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs @@ -20,7 +20,7 @@ use risingwave_hummock_sdk::prost_key_range::KeyRangeExt; use risingwave_pb::hummock::hummock_version::Levels; use risingwave_pb::hummock::{InputLevel, Level, LevelType, SstableInfo}; -use super::{CompactionInput, CompactionPicker, LocalPickerStatistic, MAX_COMPACT_LEVEL_COUNT}; +use super::{CompactionInput, CompactionPicker, LocalPickerStatistic}; use crate::hummock::compaction::overlap_strategy::OverlapStrategy; use crate::hummock::level_handler::LevelHandler; @@ -182,29 +182,52 @@ pub struct SubLevelSstables { pub struct NonOverlapSubLevelPicker { min_compaction_bytes: u64, max_compaction_bytes: u64, - min_depth: usize, + min_expected_level_count: usize, max_file_count: u64, overlap_strategy: Arc, - enable_check_task_level_overlap: bool, + max_expected_level_count: usize, } impl NonOverlapSubLevelPicker { pub fn new( min_compaction_bytes: u64, max_compaction_bytes: u64, - min_depth: usize, + min_expected_level_count: usize, max_file_count: u64, overlap_strategy: Arc, enable_check_task_level_overlap: bool, + max_expected_level_count: usize, ) -> Self { Self { min_compaction_bytes, max_compaction_bytes, - min_depth, + min_expected_level_count, max_file_count, overlap_strategy, enable_check_task_level_overlap, + max_expected_level_count, + } + } + + #[cfg(test)] + pub fn for_test( + min_compaction_bytes: u64, + max_compaction_bytes: u64, + min_expected_level_count: usize, + max_file_count: u64, + overlap_strategy: Arc, + enable_check_task_level_overlap: bool, + max_expected_level_count: usize, + ) -> Self { + Self { + min_compaction_bytes, + max_compaction_bytes, + min_expected_level_count, + max_file_count, + overlap_strategy, + enable_check_task_level_overlap, + max_expected_level_count, } } @@ -220,11 +243,9 @@ impl NonOverlapSubLevelPicker { sst: &SstableInfo, ) -> SubLevelSstables { let mut ret = SubLevelSstables { - total_file_count: 1, - total_file_size: sst.file_size, sstable_infos: vec![vec![]; levels.len()], + ..Default::default() }; - ret.sstable_infos[0].extend(vec![sst.clone()]); let mut pick_levels_range = Vec::default(); let mut max_select_level_count = 0; @@ -241,7 +262,7 @@ impl NonOverlapSubLevelPicker { .iter() .filter(|ssts| !ssts.is_empty()) .count() - > MAX_COMPACT_LEVEL_COUNT + > self.max_expected_level_count { break; } @@ -293,14 +314,6 @@ impl NonOverlapSubLevelPicker { add_files_count += 1; } - // When size / file count has exceeded the limit, we need to abandon this plan, it cannot be expanded to the last sub_level - if max_select_level_count > 1 - && (add_files_size >= self.max_compaction_bytes - || add_files_count >= self.max_file_count as usize) - { - break 'expand_new_level; - } - overlap_levels.push((reverse_index, overlap_files_range.clone())); select_level_count += 1; } @@ -309,32 +322,75 @@ impl NonOverlapSubLevelPicker { max_select_level_count = select_level_count; pick_levels_range = overlap_levels; } - } - for (reverse_index, sst_range) in pick_levels_range { - let level_ssts = &levels[reverse_index].table_infos; - ret.sstable_infos[reverse_index] = level_ssts[sst_range].to_vec(); - ret.total_file_count += ret.sstable_infos[reverse_index].len(); - ret.total_file_size += ret.sstable_infos[reverse_index] - .iter() - .map(|sst| sst.file_size) - .sum::(); + // When size / file count has exceeded the limit, we need to abandon this plan, it cannot be expanded to the last sub_level + if max_select_level_count >= self.min_expected_level_count + && (add_files_size >= self.max_compaction_bytes + || add_files_count >= self.max_file_count as usize) + { + break 'expand_new_level; + } } - // sort sst per level due to reverse expand - ret.sstable_infos.iter_mut().for_each(|level_ssts| { - level_ssts.sort_by(|sst1, sst2| { - let a = sst1.key_range.as_ref().unwrap(); - let b = sst2.key_range.as_ref().unwrap(); - a.compare(b) + if !pick_levels_range.is_empty() { + for (reverse_index, sst_range) in pick_levels_range { + let level_ssts = &levels[reverse_index].table_infos; + ret.sstable_infos[reverse_index] = level_ssts[sst_range].to_vec(); + ret.total_file_count += ret.sstable_infos[reverse_index].len(); + ret.total_file_size += ret.sstable_infos[reverse_index] + .iter() + .map(|sst| sst.file_size) + .sum::(); + } + + // sort sst per level due to reverse expand + ret.sstable_infos.iter_mut().for_each(|level_ssts| { + level_ssts.sort_by(|sst1, sst2| { + let a = sst1.key_range.as_ref().unwrap(); + let b = sst2.key_range.as_ref().unwrap(); + a.compare(b) + }); }); - }); + } else { + ret.total_file_count = 1; + ret.total_file_size = sst.file_size; + ret.sstable_infos[0].extend(vec![sst.clone()]); + } if self.enable_check_task_level_overlap { self.verify_task_level_overlap(&ret, levels); } ret.sstable_infos.retain(|ssts| !ssts.is_empty()); + + // To check whether the task is expected + if ret.total_file_size > self.max_compaction_bytes + || ret.total_file_count as u64 > self.max_file_count + || ret.sstable_infos.len() > self.max_expected_level_count + { + // rotate the sstables to meet the `max_file_count` and `max_compaction_bytes` and `max_expected_level_count` + let mut total_file_count = 0; + let mut total_file_size = 0; + let mut total_level_count = 0; + for (index, sstables) in ret.sstable_infos.iter().enumerate() { + total_file_count += sstables.len(); + total_file_size += sstables.iter().map(|sst| sst.file_size).sum::(); + total_level_count += 1; + + // Atleast `min_expected_level_count`` level should be selected + if total_level_count >= self.min_expected_level_count + && (total_file_count as u64 >= self.max_file_count + || total_file_size >= self.max_compaction_bytes + || total_level_count >= self.max_expected_level_count) + { + ret.total_file_count = total_file_count; + ret.total_file_size = total_file_size; + ret.sstable_infos.truncate(index + 1); + break; + } + } + } + ret } @@ -343,7 +399,7 @@ impl NonOverlapSubLevelPicker { l0: &[Level], level_handler: &LevelHandler, ) -> Vec { - if l0.len() < self.min_depth { + if l0.len() < self.min_expected_level_count { return vec![]; } @@ -354,7 +410,7 @@ impl NonOverlapSubLevelPicker { } let ret = self.pick_sub_level(l0, level_handler, sst); - if ret.sstable_infos.len() < self.min_depth + if ret.sstable_infos.len() < self.min_expected_level_count && ret.total_file_size < self.min_compaction_bytes { continue; @@ -370,7 +426,7 @@ impl NonOverlapSubLevelPicker { let mut unexpected = vec![]; for selected_task in scores { - if selected_task.sstable_infos.len() > MAX_COMPACT_LEVEL_COUNT { + if selected_task.sstable_infos.len() > self.max_expected_level_count { unexpected.push(selected_task); } else { expected.push(selected_task); @@ -388,10 +444,11 @@ impl NonOverlapSubLevelPicker { .then_with(|| a.total_file_size.cmp(&b.total_file_size)) }); + // For unexpected task the number of levels is as small as possible unexpected.sort_by(|a, b| { - b.sstable_infos + a.sstable_infos .len() - .cmp(&a.sstable_infos.len()) + .cmp(&b.sstable_infos.len()) .then_with(|| a.total_file_count.cmp(&b.total_file_count)) .then_with(|| a.total_file_size.cmp(&b.total_file_size)) }); @@ -477,7 +534,7 @@ impl NonOverlapSubLevelPicker { pub mod tests { use std::collections::BTreeSet; - pub use risingwave_pb::hummock::{Level, LevelType}; + use risingwave_common::config::default::compaction_config; use super::*; use crate::hummock::compaction::overlap_strategy::RangeOverlapStrategy; @@ -682,6 +739,7 @@ pub mod tests { 10000, Arc::new(RangeOverlapStrategy::default()), true, + compaction_config::max_l0_compact_level_count() as usize, ); let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); assert_eq!(6, ret.len()); @@ -696,6 +754,7 @@ pub mod tests { 10000, Arc::new(RangeOverlapStrategy::default()), true, + compaction_config::max_l0_compact_level_count() as usize, ); let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); assert_eq!(6, ret.len()); @@ -710,6 +769,7 @@ pub mod tests { 5, Arc::new(RangeOverlapStrategy::default()), true, + compaction_config::max_l0_compact_level_count() as usize, ); let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); assert_eq!(6, ret.len()); @@ -785,6 +845,7 @@ pub mod tests { 10000, Arc::new(RangeOverlapStrategy::default()), true, + compaction_config::max_l0_compact_level_count() as usize, ); let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); assert_eq!(6, ret.len()); @@ -800,6 +861,7 @@ pub mod tests { 10000, Arc::new(RangeOverlapStrategy::default()), true, + compaction_config::max_l0_compact_level_count() as usize, ); let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); assert_eq!(6, ret.len()); @@ -815,6 +877,7 @@ pub mod tests { max_file_count, Arc::new(RangeOverlapStrategy::default()), true, + compaction_config::max_l0_compact_level_count() as usize, ); let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); assert_eq!(6, ret.len()); @@ -838,6 +901,7 @@ pub mod tests { 10000, Arc::new(RangeOverlapStrategy::default()), true, + compaction_config::max_l0_compact_level_count() as usize, ); let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); assert_eq!(3, ret.len()); @@ -908,4 +972,129 @@ pub mod tests { assert_eq!(select_files.len(), 1); assert_eq!(target_files.len(), 1); } + + #[test] + fn test_pick_unexpected_task() { + let levels = vec![ + Level { + level_idx: 1, + level_type: LevelType::Nonoverlapping as i32, + table_infos: vec![generate_table(0, 1, 50, 100, 2)], // 50 + total_file_size: 50, + ..Default::default() + }, + Level { + level_idx: 2, + level_type: LevelType::Nonoverlapping as i32, + table_infos: vec![ + generate_table(1, 1, 101, 150, 1), // 50 + ], + total_file_size: 50, + ..Default::default() + }, + Level { + level_idx: 3, + level_type: LevelType::Nonoverlapping as i32, + table_infos: vec![ + generate_table(2, 1, 151, 200, 2), // 50 + ], + total_file_size: 50, + ..Default::default() + }, + Level { + level_idx: 4, + level_type: LevelType::Nonoverlapping as i32, + table_infos: vec![ + generate_table(3, 1, 50, 300, 2), // 250 + ], + total_file_size: 250, + ..Default::default() + }, + ]; + + let levels_handlers = vec![ + LevelHandler::new(0), + LevelHandler::new(1), + LevelHandler::new(2), + LevelHandler::new(3), + LevelHandler::new(4), + ]; + + { + // no limit + let picker = NonOverlapSubLevelPicker::new( + 0, + 10000, + 1, + 10000, + Arc::new(RangeOverlapStrategy::default()), + true, + compaction_config::max_l0_compact_level_count() as usize, + ); + let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); + { + let plan = &ret[0]; + assert_eq!(4, plan.sstable_infos.len()); + } + } + + { + // limit size + let picker = NonOverlapSubLevelPicker::new( + 0, + 150, + 1, + 10000, + Arc::new(RangeOverlapStrategy::default()), + true, + compaction_config::max_l0_compact_level_count() as usize, + ); + let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); + { + let plan = &ret[0]; + assert_eq!(3, plan.sstable_infos.len()); + } + } + + { + // limit count + let picker = NonOverlapSubLevelPicker::new( + 0, + 10000, + 1, + 3, + Arc::new(RangeOverlapStrategy::default()), + true, + compaction_config::max_l0_compact_level_count() as usize, + ); + let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); + { + let plan = &ret[0]; + assert_eq!(3, plan.sstable_infos.len()); + } + } + + { + // limit expected level count + let max_expected_level_count = 3; + let picker = NonOverlapSubLevelPicker::for_test( + 0, + 10000, + 1, + 3, + Arc::new(RangeOverlapStrategy::default()), + true, + max_expected_level_count, + ); + let ret = picker.pick_l0_multi_non_overlap_level(&levels, &levels_handlers[0]); + { + let plan = &ret[0]; + assert_eq!(max_expected_level_count, plan.sstable_infos.len()); + + assert_eq!(0, plan.sstable_infos[0][0].sst_id); + assert_eq!(1, plan.sstable_infos[1][0].sst_id); + assert_eq!(2, plan.sstable_infos[2][0].sst_id); + } + } + } } diff --git a/src/meta/src/hummock/compaction/picker/mod.rs b/src/meta/src/hummock/compaction/picker/mod.rs index 7e33123272684..6d464b9a33bcd 100644 --- a/src/meta/src/hummock/compaction/picker/mod.rs +++ b/src/meta/src/hummock/compaction/picker/mod.rs @@ -43,8 +43,6 @@ pub use ttl_reclaim_compaction_picker::{TtlPickerState, TtlReclaimCompactionPick use crate::hummock::level_handler::LevelHandler; -pub const MAX_COMPACT_LEVEL_COUNT: usize = 42; - #[derive(Default, Debug)] pub struct LocalPickerStatistic { pub skip_by_write_amp_limit: u64, @@ -97,18 +95,3 @@ pub trait CompactionPicker { stats: &mut LocalPickerStatistic, ) -> Option; } - -#[derive(Default, Clone, Debug)] -pub struct PartitionLevelInfo { - pub level_id: u32, - pub sub_level_id: u64, - pub left_idx: usize, - pub right_idx: usize, - pub total_file_size: u64, -} - -#[derive(Default, Clone, Debug)] -pub struct LevelPartition { - pub sub_levels: Vec, - pub total_file_size: u64, -} diff --git a/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs index 5d05fedbe5338..b94f7587dd04e 100644 --- a/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs @@ -24,7 +24,7 @@ use crate::hummock::level_handler::LevelHandler; // key_range and selects the appropriate files to generate compaction pub struct SpaceReclaimCompactionPicker { // config - pub max_space_reclaim_bytes: u64, + pub _max_space_reclaim_bytes: u64, // for filter pub all_table_ids: HashSet, @@ -40,7 +40,7 @@ pub struct SpaceReclaimPickerState { impl SpaceReclaimCompactionPicker { pub fn new(max_space_reclaim_bytes: u64, all_table_ids: HashSet) -> Self { Self { - max_space_reclaim_bytes, + _max_space_reclaim_bytes: max_space_reclaim_bytes, all_table_ids, } } @@ -168,10 +168,11 @@ impl SpaceReclaimCompactionPicker { #[cfg(test)] mod test { - use std::collections::HashMap; + use std::collections::{BTreeSet, HashMap}; use std::sync::Arc; use itertools::Itertools; + use risingwave_common::catalog::TableId; use risingwave_pb::hummock::compact_task; pub use risingwave_pb::hummock::{Level, LevelType}; @@ -234,11 +235,12 @@ mod test { } assert_eq!(levels.len(), 4); - let mut levels = Levels { + let levels = Levels { levels, l0: Some(l0), ..Default::default() }; + let mut member_table_ids = BTreeSet::new(); let mut levels_handler = (0..5).map(LevelHandler::new).collect_vec(); let mut local_stats = LocalSelectorStatistic::default(); @@ -252,6 +254,7 @@ mod test { 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), @@ -268,6 +271,7 @@ mod test { 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), @@ -308,6 +312,7 @@ mod test { 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), @@ -334,6 +339,7 @@ mod test { 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), @@ -351,12 +357,17 @@ mod test { } } - levels.member_table_ids = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + member_table_ids = BTreeSet::from_iter( + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + .into_iter() + .map(TableId::new), + ); // pick space reclaim let task = selector.pick_compaction( 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), @@ -372,13 +383,15 @@ mod test { } } - levels.member_table_ids = vec![2, 3, 4, 5, 6, 7, 8, 9]; + member_table_ids = + BTreeSet::from_iter([2, 3, 4, 5, 6, 7, 8, 9].into_iter().map(TableId::new)); // pick space reclaim let task = selector .pick_compaction( 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), @@ -407,7 +420,7 @@ mod test { // rebuild selector selector = SpaceReclaimCompactionSelector::default(); // cut range [3,4] [6] [8,9,10] - levels.member_table_ids = vec![0, 1, 2, 5, 7]; + member_table_ids = BTreeSet::from_iter([0, 1, 2, 5, 7].into_iter().map(TableId::new)); let expect_task_file_count = [2, 1, 4]; let expect_task_sst_id_range = [vec![3, 4], vec![6], vec![8, 9, 10, 11]]; for (index, x) in expect_task_file_count.iter().enumerate() { @@ -417,6 +430,7 @@ mod test { 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), @@ -458,12 +472,13 @@ mod test { // rebuild selector selector = SpaceReclaimCompactionSelector::default(); // cut range [3,4] [6] [8,9,10] - levels.member_table_ids = vec![0, 1, 2, 5, 7]; + + member_table_ids = BTreeSet::from_iter([0, 1, 2, 5, 7].into_iter().map(TableId::new)); let expect_task_file_count = [2, 1, 5]; let expect_task_sst_id_range = [vec![3, 4], vec![6], vec![7, 8, 9, 10, 11]]; for (index, x) in expect_task_file_count.iter().enumerate() { if index == expect_task_file_count.len() - 1 { - levels.member_table_ids = vec![2, 5]; + member_table_ids = BTreeSet::from_iter([2, 5].into_iter().map(TableId::new)); } // // pick space reclaim @@ -472,6 +487,7 @@ mod test { 1, &group_config, &levels, + &member_table_ids, &mut levels_handler, &mut local_stats, HashMap::default(), diff --git a/src/meta/src/hummock/compaction/picker/tier_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/tier_compaction_picker.rs index 9ed22ba551fcc..ea25211b44749 100644 --- a/src/meta/src/hummock/compaction/picker/tier_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/tier_compaction_picker.rs @@ -21,7 +21,6 @@ use super::{ CompactionInput, CompactionPicker, CompactionTaskValidator, LocalPickerStatistic, ValidationRuleType, }; -use crate::hummock::compaction::picker::MAX_COMPACT_LEVEL_COUNT; use crate::hummock::level_handler::LevelHandler; pub struct TierCompactionPicker { @@ -87,10 +86,7 @@ impl TierCompactionPicker { let mut compaction_bytes = level.total_file_size; let mut compact_file_count = level.table_infos.len() as u64; // Limit sstable file count to avoid using too much memory. - let overlapping_max_compact_file_numer = std::cmp::min( - self.config.level0_max_compact_file_number, - MAX_COMPACT_LEVEL_COUNT as u64, - ); + let overlapping_max_compact_file_numer = self.config.level0_max_compact_file_number; for other in &l0.sub_levels[idx + 1..] { if compaction_bytes > max_compaction_bytes { @@ -295,7 +291,6 @@ pub mod tests { sub_levels: vec![l1, l2], }), levels: vec![], - member_table_ids: vec![1], ..Default::default() }; let config = Arc::new( diff --git a/src/meta/src/hummock/compaction/picker/tombstone_reclaim_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/tombstone_reclaim_compaction_picker.rs index f07a8872c9b03..c7be0347fce61 100644 --- a/src/meta/src/hummock/compaction/picker/tombstone_reclaim_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/tombstone_reclaim_compaction_picker.rs @@ -154,7 +154,6 @@ pub mod tests { ], ), ], - member_table_ids: vec![1], ..Default::default() }; let levels_handler = vec![ diff --git a/src/meta/src/hummock/compaction/picker/ttl_reclaim_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/ttl_reclaim_compaction_picker.rs index f690b0f80112c..bc1fc2ce304be 100644 --- a/src/meta/src/hummock/compaction/picker/ttl_reclaim_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/ttl_reclaim_compaction_picker.rs @@ -201,6 +201,7 @@ impl TtlReclaimCompactionPicker { #[cfg(test)] mod test { + use std::collections::BTreeSet; use std::sync::Arc; use itertools::Itertools; @@ -377,6 +378,7 @@ mod test { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, table_id_to_options, @@ -427,6 +429,7 @@ mod test { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, table_id_to_options.clone(), @@ -460,6 +463,7 @@ mod test { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, table_id_to_options.clone(), @@ -516,6 +520,7 @@ mod test { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, table_id_to_options, @@ -557,6 +562,7 @@ mod test { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, HashMap::default(), @@ -619,6 +625,7 @@ mod test { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, table_id_to_options.clone(), @@ -711,6 +718,7 @@ mod test { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handler, &mut local_stats, table_id_to_options.clone(), diff --git a/src/meta/src/hummock/compaction/selector/emergency_selector.rs b/src/meta/src/hummock/compaction/selector/emergency_selector.rs index c386aee5c8644..685ab1487d51c 100644 --- a/src/meta/src/hummock/compaction/selector/emergency_selector.rs +++ b/src/meta/src/hummock/compaction/selector/emergency_selector.rs @@ -37,6 +37,7 @@ impl CompactionSelector for EmergencySelector { task_id: HummockCompactionTaskId, group: &CompactionGroup, levels: &Levels, + _member_table_ids: &std::collections::BTreeSet, level_handlers: &mut [LevelHandler], selector_stats: &mut LocalSelectorStatistic, _table_id_to_options: HashMap, diff --git a/src/meta/src/hummock/compaction/selector/level_selector.rs b/src/meta/src/hummock/compaction/selector/level_selector.rs index 5c118269cfee3..38d1e35e22502 100644 --- a/src/meta/src/hummock/compaction/selector/level_selector.rs +++ b/src/meta/src/hummock/compaction/selector/level_selector.rs @@ -314,14 +314,9 @@ impl DynamicLevelSelectorCore { if level_idx < ctx.base_level || level_idx >= self.config.max_level as usize { continue; } - let upper_level = if level_idx == ctx.base_level { - 0 - } else { - level_idx - 1 - }; - let total_size = level.total_file_size - + handlers[upper_level].get_pending_output_file_size(level.level_idx) - - handlers[level_idx].get_pending_output_file_size(level.level_idx + 1); + let output_file_size = + handlers[level_idx].get_pending_output_file_size(level.level_idx + 1); + let total_size = level.total_file_size.saturating_sub(output_file_size); if total_size == 0 { continue; } @@ -427,6 +422,7 @@ impl CompactionSelector for DynamicLevelSelector { task_id: HummockCompactionTaskId, compaction_group: &CompactionGroup, levels: &Levels, + _member_table_ids: &std::collections::BTreeSet, level_handlers: &mut [LevelHandler], selector_stats: &mut LocalSelectorStatistic, _table_id_to_options: HashMap, @@ -483,7 +479,7 @@ impl CompactionSelector for DynamicLevelSelector { #[cfg(test)] pub mod tests { - use std::collections::HashMap; + use std::collections::{BTreeSet, HashMap}; use std::sync::Arc; use itertools::Itertools; @@ -588,7 +584,7 @@ pub mod tests { .max_compaction_bytes(10000) .level0_tier_compact_file_number(4) .compaction_mode(CompactionMode::Range as i32) - .level0_sub_level_compact_level_count(1) + .level0_sub_level_compact_level_count(3) .build(); let group_config = CompactionGroup::new(1, config.clone()); let levels = vec![ @@ -605,7 +601,6 @@ pub mod tests { 3, 10, ))), - member_table_ids: vec![1], ..Default::default() }; @@ -617,6 +612,7 @@ pub mod tests { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handlers, &mut local_stats, HashMap::default(), @@ -644,6 +640,7 @@ pub mod tests { 1, &group_config, &levels, + &BTreeSet::new(), &mut levels_handlers, &mut local_stats, HashMap::default(), @@ -663,6 +660,7 @@ pub mod tests { 2, &group_config, &levels, + &BTreeSet::new(), &mut levels_handlers, &mut local_stats, HashMap::default(), @@ -699,6 +697,7 @@ pub mod tests { 2, &group_config, &levels, + &BTreeSet::new(), &mut levels_handlers, &mut local_stats, HashMap::default(), diff --git a/src/meta/src/hummock/compaction/selector/manual_selector.rs b/src/meta/src/hummock/compaction/selector/manual_selector.rs index 427efadf3914d..62c94c8f888df 100644 --- a/src/meta/src/hummock/compaction/selector/manual_selector.rs +++ b/src/meta/src/hummock/compaction/selector/manual_selector.rs @@ -79,6 +79,7 @@ impl CompactionSelector for ManualCompactionSelector { task_id: HummockCompactionTaskId, group: &CompactionGroup, levels: &Levels, + _member_table_ids: &std::collections::BTreeSet, level_handlers: &mut [LevelHandler], _selector_stats: &mut LocalSelectorStatistic, _table_id_to_options: HashMap, diff --git a/src/meta/src/hummock/compaction/selector/mod.rs b/src/meta/src/hummock/compaction/selector/mod.rs index a342a661ecb7b..aca2457da62dc 100644 --- a/src/meta/src/hummock/compaction/selector/mod.rs +++ b/src/meta/src/hummock/compaction/selector/mod.rs @@ -24,13 +24,13 @@ mod space_reclaim_selector; mod tombstone_compaction_selector; mod ttl_selector; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::sync::Arc; pub use emergency_selector::EmergencySelector; pub use level_selector::{DynamicLevelSelector, DynamicLevelSelectorCore}; pub use manual_selector::{ManualCompactionOption, ManualCompactionSelector}; -use risingwave_common::catalog::TableOption; +use risingwave_common::catalog::{TableId, TableOption}; use risingwave_hummock_sdk::HummockCompactionTaskId; use risingwave_pb::hummock::compact_task; use risingwave_pb::hummock::hummock_version::Levels; @@ -53,6 +53,7 @@ pub trait CompactionSelector: Sync + Send { task_id: HummockCompactionTaskId, group: &CompactionGroup, levels: &Levels, + member_table_ids: &BTreeSet, level_handlers: &mut [LevelHandler], selector_stats: &mut LocalSelectorStatistic, table_id_to_options: HashMap, diff --git a/src/meta/src/hummock/compaction/selector/space_reclaim_selector.rs b/src/meta/src/hummock/compaction/selector/space_reclaim_selector.rs index c48cb0fe605c5..b284a6a538b3e 100644 --- a/src/meta/src/hummock/compaction/selector/space_reclaim_selector.rs +++ b/src/meta/src/hummock/compaction/selector/space_reclaim_selector.rs @@ -17,10 +17,10 @@ // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::sync::Arc; -use risingwave_common::catalog::TableOption; +use risingwave_common::catalog::{TableId, TableOption}; use risingwave_hummock_sdk::HummockCompactionTaskId; use risingwave_pb::hummock::compact_task; use risingwave_pb::hummock::hummock_version::Levels; @@ -44,6 +44,7 @@ impl CompactionSelector for SpaceReclaimCompactionSelector { task_id: HummockCompactionTaskId, group: &CompactionGroup, levels: &Levels, + member_table_ids: &BTreeSet, level_handlers: &mut [LevelHandler], _selector_stats: &mut LocalSelectorStatistic, _table_id_to_options: HashMap, @@ -53,7 +54,10 @@ impl CompactionSelector for SpaceReclaimCompactionSelector { DynamicLevelSelectorCore::new(group.compaction_config.clone(), developer_config); let mut picker = SpaceReclaimCompactionPicker::new( group.compaction_config.max_space_reclaim_bytes, - levels.member_table_ids.iter().cloned().collect(), + member_table_ids + .iter() + .map(|table_id| table_id.table_id) + .collect(), ); let ctx = dynamic_level_core.calculate_level_base_size(levels); let state = self.state.entry(group.group_id).or_default(); diff --git a/src/meta/src/hummock/compaction/selector/tombstone_compaction_selector.rs b/src/meta/src/hummock/compaction/selector/tombstone_compaction_selector.rs index 5cbae609caa86..b05802513733d 100644 --- a/src/meta/src/hummock/compaction/selector/tombstone_compaction_selector.rs +++ b/src/meta/src/hummock/compaction/selector/tombstone_compaction_selector.rs @@ -42,6 +42,7 @@ impl CompactionSelector for TombstoneCompactionSelector { task_id: HummockCompactionTaskId, group: &CompactionGroup, levels: &Levels, + _member_table_ids: &std::collections::BTreeSet, level_handlers: &mut [LevelHandler], _selector_stats: &mut LocalSelectorStatistic, _table_id_to_options: HashMap, diff --git a/src/meta/src/hummock/compaction/selector/ttl_selector.rs b/src/meta/src/hummock/compaction/selector/ttl_selector.rs index ed099ede4158f..0e9497b06b17d 100644 --- a/src/meta/src/hummock/compaction/selector/ttl_selector.rs +++ b/src/meta/src/hummock/compaction/selector/ttl_selector.rs @@ -44,6 +44,7 @@ impl CompactionSelector for TtlCompactionSelector { task_id: HummockCompactionTaskId, group: &CompactionGroup, levels: &Levels, + _member_table_ids: &std::collections::BTreeSet, level_handlers: &mut [LevelHandler], _selector_stats: &mut LocalSelectorStatistic, table_id_to_options: HashMap, diff --git a/src/meta/src/hummock/compactor_manager.rs b/src/meta/src/hummock/compactor_manager.rs index fcbac63817fff..ab0c868f703c4 100644 --- a/src/meta/src/hummock/compactor_manager.rs +++ b/src/meta/src/hummock/compactor_manager.rs @@ -101,6 +101,13 @@ impl Compactor { Ok(()) } + pub fn cancel_tasks(&self, task_ids: &Vec) -> MetaResult<()> { + for task_id in task_ids { + self.cancel_task(*task_id)?; + } + Ok(()) + } + pub fn context_id(&self) -> HummockContextId { self.context_id } @@ -111,12 +118,12 @@ impl Compactor { /// `CompactTaskAssignment`. /// /// A compact task can be in one of these states: -/// - 1. Success: an assigned task is reported as success via `CompactStatus::report_compact_task`. -/// It's the final state. -/// - 2. Failed: an Failed task is reported as success via `CompactStatus::report_compact_task`. -/// It's the final state. -/// - 3. Cancelled: a task is reported as cancelled via `CompactStatus::report_compact_task`. It's -/// the final state. +/// 1. Success: an assigned task is reported as success via `CompactStatus::report_compact_task`. +/// It's the final state. +/// 2. Failed: an Failed task is reported as success via `CompactStatus::report_compact_task`. +/// It's the final state. +/// 3. Cancelled: a task is reported as cancelled via `CompactStatus::report_compact_task`. It's +/// the final state. pub struct CompactorManagerInner { pub task_expired_seconds: u64, pub heartbeat_expired_seconds: u64, @@ -131,7 +138,7 @@ impl CompactorManagerInner { use risingwave_meta_model_v2::compaction_task; use sea_orm::EntityTrait; // Retrieve the existing task assignments from metastore. - let task_assignment: Vec = match env.meta_store() { + let task_assignment: Vec = match env.meta_store_ref() { MetaStoreImpl::Kv(meta_store) => CompactTaskAssignment::list(meta_store).await?, MetaStoreImpl::Sql(sql_meta_store) => compaction_task::Entity::find() .all(&sql_meta_store.conn) diff --git a/src/meta/src/hummock/manager/checkpoint.rs b/src/meta/src/hummock/manager/checkpoint.rs index 67ab7ebcf047d..70bbef6bd3db2 100644 --- a/src/meta/src/hummock/manager/checkpoint.rs +++ b/src/meta/src/hummock/manager/checkpoint.rs @@ -17,7 +17,6 @@ use std::ops::Bound::{Excluded, Included}; use std::ops::{Deref, DerefMut}; use std::sync::atomic::Ordering; -use function_name::named; use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ object_size_map, summarize_group_deltas, }; @@ -28,11 +27,11 @@ use risingwave_pb::hummock::hummock_version_checkpoint::{ }; use risingwave_pb::hummock::{PbHummockVersionArchive, PbHummockVersionCheckpoint}; use thiserror_ext::AsReport; +use tracing::warn; use crate::hummock::error::Result; use crate::hummock::manager::versioning::Versioning; -use crate::hummock::manager::{read_lock, write_lock}; -use crate::hummock::metrics_utils::trigger_gc_stat; +use crate::hummock::metrics_utils::{trigger_gc_stat, trigger_split_stat}; use crate::hummock::HummockManager; #[derive(Default)] @@ -121,11 +120,10 @@ impl HummockManager { /// Returns the diff between new and old checkpoint id. /// Note that this method must not be called concurrently, because internally it doesn't hold /// lock throughout the method. - #[named] pub async fn create_version_checkpoint(&self, min_delta_log_num: u64) -> Result { let timer = self.metrics.version_checkpoint_latency.start_timer(); // 1. hold read lock and create new checkpoint - let versioning_guard = read_lock!(self, versioning).await; + let versioning_guard = self.versioning.read().await; let versioning: &Versioning = versioning_guard.deref(); let current_version: &HummockVersion = &versioning.current_version; let old_checkpoint: &HummockVersionCheckpoint = &versioning.checkpoint; @@ -136,9 +134,10 @@ impl HummockManager { } if cfg!(test) && new_checkpoint_id == old_checkpoint_id { drop(versioning_guard); - let mut versioning = write_lock!(self, versioning).await; - versioning.mark_objects_for_deletion(); - let min_pinned_version_id = versioning.min_pinned_version_id(); + let versioning = self.versioning.read().await; + let context_info = self.context_info.read().await; + versioning.mark_objects_for_deletion(&context_info, &self.delete_object_tracker); + let min_pinned_version_id = context_info.min_pinned_version_id(); trigger_gc_stat(&self.metrics, &versioning.checkpoint, min_pinned_version_id); return Ok(0); } @@ -159,19 +158,35 @@ impl HummockManager { summary .insert_table_infos .iter() - .map(|t| (t.object_id, t.file_size)), + .map(|t| (t.object_id, t.file_size)) + .chain( + version_delta + .change_log_delta + .values() + .flat_map(|change_log| { + let new_log = change_log.new_log.as_ref().unwrap(); + new_log + .new_value + .iter() + .chain(new_log.old_value.iter()) + .map(|t| (t.object_id, t.file_size)) + }), + ), ); } - versions_object_ids.extend(version_delta.newly_added_object_ids()); } // Object ids that once exist in any hummock version but not exist in the latest hummock version let removed_object_ids = &versions_object_ids - ¤t_version.get_object_ids(); - let total_file_size = removed_object_ids .iter() - .map(|t| object_sizes.get(t).copied().unwrap()) + .map(|t| { + object_sizes.get(t).copied().unwrap_or_else(|| { + warn!(object_id = t, "unable to get size of removed object id"); + 0 + }) + }) .sum::(); stale_objects.insert( current_version.id, @@ -207,17 +222,19 @@ impl HummockManager { } } // 3. hold write lock and update in memory state - let mut versioning_guard = write_lock!(self, versioning).await; + let mut versioning_guard = self.versioning.write().await; let versioning = versioning_guard.deref_mut(); + let context_info = self.context_info.read().await; assert!(new_checkpoint.version.id > versioning.checkpoint.version.id); versioning.checkpoint = new_checkpoint; // Not delete stale objects when archive is enabled if !self.env.opts.enable_hummock_data_archive { - versioning.mark_objects_for_deletion(); + versioning.mark_objects_for_deletion(&context_info, &self.delete_object_tracker); } - let min_pinned_version_id = versioning.min_pinned_version_id(); + let min_pinned_version_id = context_info.min_pinned_version_id(); trigger_gc_stat(&self.metrics, &versioning.checkpoint, min_pinned_version_id); + trigger_split_stat(&self.metrics, &versioning.current_version); drop(versioning_guard); timer.observe_duration(); self.metrics @@ -242,9 +259,8 @@ impl HummockManager { self.pause_version_checkpoint.load(Ordering::Relaxed) } - #[named] pub async fn get_checkpoint_version(&self) -> HummockVersion { - let versioning_guard = read_lock!(self, versioning).await; + let versioning_guard = self.versioning.read().await; versioning_guard.checkpoint.version.clone() } } diff --git a/src/meta/src/hummock/manager/commit_epoch.rs b/src/meta/src/hummock/manager/commit_epoch.rs new file mode 100644 index 0000000000000..9a494bc509b4f --- /dev/null +++ b/src/meta/src/hummock/manager/commit_epoch.rs @@ -0,0 +1,489 @@ +// Copyright 2024 RisingWave Labs +// +// 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::{BTreeMap, HashMap, HashSet}; + +use itertools::Itertools; +use risingwave_common::catalog::TableId; +use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId; +use risingwave_hummock_sdk::table_stats::{ + add_prost_table_stats_map, purge_prost_table_stats, PbTableStatsMap, +}; +use risingwave_hummock_sdk::table_watermark::TableWatermarks; +use risingwave_hummock_sdk::{ + CompactionGroupId, ExtendedSstableInfo, HummockContextId, HummockEpoch, HummockSstableObjectId, +}; +use risingwave_pb::hummock::compact_task::{self}; +use risingwave_pb::hummock::group_delta::DeltaType; +use risingwave_pb::hummock::hummock_version_delta::ChangeLogDelta; +use risingwave_pb::hummock::{GroupDelta, HummockSnapshot, IntraLevelDelta, StateTableInfoDelta}; + +use crate::hummock::error::{Error, Result}; +use crate::hummock::manager::transaction::{ + HummockVersionStatsTransaction, HummockVersionTransaction, +}; +use crate::hummock::manager::versioning::Versioning; +use crate::hummock::metrics_utils::{ + get_or_create_local_table_stat, trigger_local_table_stat, trigger_sst_stat, +}; +use crate::hummock::sequence::next_sstable_object_id; +use crate::hummock::{commit_multi_var, start_measure_real_process_timer, HummockManager}; + +#[derive(Debug, Clone)] +pub struct NewTableFragmentInfo { + pub table_id: TableId, + pub mv_table_id: Option, + pub internal_table_ids: Vec, +} + +pub struct CommitEpochInfo { + pub sstables: Vec, + pub new_table_watermarks: HashMap, + pub sst_to_context: HashMap, + pub new_table_fragment_info: Option, + pub change_log_delta: HashMap, + pub table_committed_epoch: BTreeMap>, + pub max_committed_epoch: HummockEpoch, +} + +impl CommitEpochInfo { + pub fn new( + sstables: Vec, + new_table_watermarks: HashMap, + sst_to_context: HashMap, + new_table_fragment_info: Option, + change_log_delta: HashMap, + table_committed_epoch: BTreeMap>, + max_committed_epoch: HummockEpoch, + ) -> Self { + Self { + sstables, + new_table_watermarks, + sst_to_context, + new_table_fragment_info, + change_log_delta, + table_committed_epoch, + max_committed_epoch, + } + } +} + +impl HummockManager { + #[cfg(any(test, feature = "test"))] + pub async fn commit_epoch_for_test( + &self, + epoch: HummockEpoch, + sstables: Vec>, + sst_to_context: HashMap, + ) -> Result<()> { + let tables = self + .versioning + .read() + .await + .current_version + .state_table_info + .info() + .keys() + .cloned() + .collect(); + let info = CommitEpochInfo::new( + sstables.into_iter().map(Into::into).collect(), + HashMap::new(), + sst_to_context, + None, + HashMap::new(), + BTreeMap::from_iter([(epoch, tables)]), + epoch, + ); + self.commit_epoch(info).await?; + Ok(()) + } + + /// Caller should ensure `epoch` > `max_committed_epoch` + pub async fn commit_epoch( + &self, + commit_info: CommitEpochInfo, + ) -> Result> { + let CommitEpochInfo { + mut sstables, + new_table_watermarks, + sst_to_context, + new_table_fragment_info, + change_log_delta, + table_committed_epoch, + max_committed_epoch: epoch, + } = commit_info; + let mut versioning_guard = self.versioning.write().await; + let _timer = start_measure_real_process_timer!(self, "commit_epoch"); + // Prevent commit new epochs if this flag is set + if versioning_guard.disable_commit_epochs { + return Ok(None); + } + + let versioning: &mut Versioning = &mut versioning_guard; + self.commit_epoch_sanity_check( + epoch, + &sstables, + &sst_to_context, + &versioning.current_version, + ) + .await?; + + // Consume and aggregate table stats. + let mut table_stats_change = PbTableStatsMap::default(); + for s in &mut sstables { + add_prost_table_stats_map(&mut table_stats_change, &std::mem::take(&mut s.table_stats)); + } + + let mut version = HummockVersionTransaction::new( + &mut versioning.current_version, + &mut versioning.hummock_version_deltas, + self.env.notification_manager(), + &self.metrics, + ); + let mut new_version_delta = version.new_delta(); + + new_version_delta.max_committed_epoch = epoch; + new_version_delta.new_table_watermarks = new_table_watermarks; + new_version_delta.change_log_delta = change_log_delta; + + let mut table_compaction_group_mapping = new_version_delta + .latest_version() + .state_table_info + .build_table_compaction_group_id(); + + let mut new_table_ids = None; + + // Add new table + if let Some(new_fragment_table_info) = new_table_fragment_info { + let new_table_ids = new_table_ids.insert(HashMap::new()); + if !new_fragment_table_info.internal_table_ids.is_empty() { + for table_id in &new_fragment_table_info.internal_table_ids { + if let Some(info) = new_version_delta + .latest_version() + .state_table_info + .info() + .get(table_id) + { + return Err(Error::CompactionGroup(format!( + "table {} already exist {:?}", + table_id.table_id, info, + ))); + } + } + + for table_id in &new_fragment_table_info.internal_table_ids { + table_compaction_group_mapping + .insert(*table_id, StaticCompactionGroupId::StateDefault as u64); + new_table_ids.insert(*table_id, StaticCompactionGroupId::StateDefault as u64); + } + } + + if let Some(table_id) = new_fragment_table_info.mv_table_id { + if let Some(info) = new_version_delta + .latest_version() + .state_table_info + .info() + .get(&table_id) + { + return Err(Error::CompactionGroup(format!( + "table {} already exist {:?}", + table_id.table_id, info, + ))); + } + let _ = table_compaction_group_mapping + .insert(table_id, StaticCompactionGroupId::MaterializedView as u64); + new_table_ids.insert(table_id, StaticCompactionGroupId::MaterializedView as u64); + } + } + + let mut incorrect_ssts = vec![]; + let mut new_sst_id_number = 0; + for ExtendedSstableInfo { + compaction_group_id, + sst_info: sst, + .. + } in &mut sstables + { + let is_sst_belong_to_group_declared = match new_version_delta + .latest_version() + .levels + .get(compaction_group_id) + { + Some(_compaction_group) => sst.table_ids.iter().all(|t| { + table_compaction_group_mapping + .get(&TableId::new(*t)) + .map(|table_cg_id| table_cg_id == compaction_group_id) + .unwrap_or(false) + }), + None => false, + }; + if !is_sst_belong_to_group_declared { + let mut group_table_ids: BTreeMap<_, Vec> = BTreeMap::new(); + for table_id in sst.get_table_ids() { + match table_compaction_group_mapping.get(&TableId::new(*table_id)) { + Some(compaction_group_id) => { + group_table_ids + .entry(*compaction_group_id) + .or_default() + .push(*table_id); + } + None => { + tracing::warn!( + "table {} in SST {} doesn't belong to any compaction group", + table_id, + sst.get_object_id(), + ); + } + } + } + let is_trivial_adjust = group_table_ids.len() == 1 + && group_table_ids.first_key_value().unwrap().1.len() + == sst.get_table_ids().len(); + if is_trivial_adjust { + *compaction_group_id = *group_table_ids.first_key_value().unwrap().0; + // is_sst_belong_to_group_declared = true; + } else { + new_sst_id_number += group_table_ids.len(); + incorrect_ssts.push((std::mem::take(sst), group_table_ids)); + *compaction_group_id = + StaticCompactionGroupId::NewCompactionGroup as CompactionGroupId; + } + } + } + let mut new_sst_id = next_sstable_object_id(&self.env, new_sst_id_number).await?; + let original_sstables = std::mem::take(&mut sstables); + sstables.reserve_exact(original_sstables.len() - incorrect_ssts.len() + new_sst_id_number); + let mut incorrect_ssts = incorrect_ssts.into_iter(); + for original_sstable in original_sstables { + if original_sstable.compaction_group_id + == StaticCompactionGroupId::NewCompactionGroup as CompactionGroupId + { + let (sst, group_table_ids) = incorrect_ssts.next().unwrap(); + let mut branch_groups = HashMap::new(); + for (group_id, _match_ids) in group_table_ids { + let mut branch_sst = sst.clone(); + branch_sst.sst_id = new_sst_id; + sstables.push(ExtendedSstableInfo::with_compaction_group( + group_id, branch_sst, + )); + branch_groups.insert(group_id, new_sst_id); + new_sst_id += 1; + } + } else { + sstables.push(original_sstable); + } + } + + let mut modified_compaction_groups = vec![]; + // Append SSTs to a new version. + for (compaction_group_id, sstables) in &sstables + .into_iter() + // the sort is stable sort, and will not change the order within compaction group. + // Do a sort so that sst in the same compaction group can be consecutive + .sorted_by_key( + |ExtendedSstableInfo { + compaction_group_id, + .. + }| *compaction_group_id, + ) + .group_by( + |ExtendedSstableInfo { + compaction_group_id, + .. + }| *compaction_group_id, + ) + { + modified_compaction_groups.push(compaction_group_id); + let group_sstables = sstables + .into_iter() + .map(|ExtendedSstableInfo { sst_info, .. }| sst_info) + .collect_vec(); + + let group_deltas = &mut new_version_delta + .group_deltas + .entry(compaction_group_id) + .or_default() + .group_deltas; + let l0_sub_level_id = epoch; + let group_delta = GroupDelta { + delta_type: Some(DeltaType::IntraLevel(IntraLevelDelta { + level_idx: 0, + inserted_table_infos: group_sstables.clone(), + l0_sub_level_id, + ..Default::default() + })), + }; + group_deltas.push(group_delta); + } + + // update state table info + new_version_delta.with_latest_version(|version, delta| { + if let Some(new_table_ids) = new_table_ids { + for (table_id, cg_id) in new_table_ids { + delta.state_table_info_delta.insert( + table_id, + StateTableInfoDelta { + committed_epoch: epoch, + safe_epoch: epoch, + compaction_group_id: cg_id, + }, + ); + } + } + for (table_id, info) in version.state_table_info.info() { + assert!( + delta + .state_table_info_delta + .insert( + *table_id, + StateTableInfoDelta { + committed_epoch: epoch, + safe_epoch: info.safe_epoch, + compaction_group_id: info.compaction_group_id, + } + ) + .is_none(), + "newly added table exists previously: {:?}", + table_id + ); + } + }); + + new_version_delta.pre_apply(); + + // TODO: remove the sanity check when supporting partial checkpoint + assert_eq!(1, table_committed_epoch.len()); + assert_eq!( + table_committed_epoch.iter().next().expect("non-empty"), + ( + &epoch, + &version + .latest_version() + .state_table_info + .info() + .keys() + .cloned() + .collect() + ) + ); + + // Apply stats changes. + let mut version_stats = HummockVersionStatsTransaction::new( + &mut versioning.version_stats, + self.env.notification_manager(), + ); + add_prost_table_stats_map(&mut version_stats.table_stats, &table_stats_change); + if purge_prost_table_stats(&mut version_stats.table_stats, version.latest_version()) { + self.metrics.version_stats.reset(); + versioning.local_metrics.clear(); + } + + trigger_local_table_stat( + &self.metrics, + &mut versioning.local_metrics, + &version_stats, + &table_stats_change, + ); + for (table_id, stats) in &table_stats_change { + if stats.total_key_size == 0 + && stats.total_value_size == 0 + && stats.total_key_count == 0 + { + continue; + } + let stats_value = std::cmp::max(0, stats.total_key_size + stats.total_value_size); + let table_metrics = get_or_create_local_table_stat( + &self.metrics, + *table_id, + &mut versioning.local_metrics, + ); + table_metrics.inc_write_throughput(stats_value as u64); + } + commit_multi_var!(self.meta_store_ref(), version, version_stats)?; + + let snapshot = HummockSnapshot { + committed_epoch: epoch, + current_epoch: epoch, + }; + let prev_snapshot = self.latest_snapshot.swap(snapshot.clone().into()); + assert!(prev_snapshot.committed_epoch < epoch); + assert!(prev_snapshot.current_epoch < epoch); + + for compaction_group_id in &modified_compaction_groups { + trigger_sst_stat( + &self.metrics, + None, + &versioning.current_version, + *compaction_group_id, + ); + } + + tracing::trace!("new committed epoch {}", epoch); + + let mut table_groups = HashMap::::default(); + for (table_id, info) in versioning.current_version.state_table_info.info() { + table_groups.insert( + table_id.table_id, + versioning + .current_version + .state_table_info + .compaction_group_member_tables() + .get(&info.compaction_group_id) + .expect("should exist") + .len(), + ); + } + drop(versioning_guard); + // Don't trigger compactions if we enable deterministic compaction + if !self.env.opts.compaction_deterministic_test { + // commit_epoch may contains SSTs from any compaction group + for id in &modified_compaction_groups { + self.try_send_compaction_request(*id, compact_task::TaskType::Dynamic); + } + if !table_stats_change.is_empty() { + table_stats_change.retain(|table_id, _| { + table_groups + .get(table_id) + .map(|table_count| *table_count > 1) + .unwrap_or(false) + }); + } + if !table_stats_change.is_empty() { + self.collect_table_write_throughput(table_stats_change); + } + } + if !modified_compaction_groups.is_empty() { + self.try_update_write_limits(&modified_compaction_groups) + .await; + } + #[cfg(test)] + { + self.check_state_consistency().await; + } + Ok(Some(snapshot)) + } + + fn collect_table_write_throughput(&self, table_stats: PbTableStatsMap) { + let mut table_infos = self.history_table_throughput.write(); + for (table_id, stat) in table_stats { + let throughput = (stat.total_value_size + stat.total_key_size) as u64; + let entry = table_infos.entry(table_id).or_default(); + entry.push_back(throughput); + if entry.len() > self.env.opts.table_info_statistic_history_times { + entry.pop_front(); + } + } + } +} diff --git a/src/meta/src/hummock/manager/compaction.rs b/src/meta/src/hummock/manager/compaction.rs index 39dd44565865d..906824c155f7a 100644 --- a/src/meta/src/hummock/manager/compaction.rs +++ b/src/meta/src/hummock/manager/compaction.rs @@ -12,37 +12,212 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::sync::Arc; +// Copyright 2024 RisingWave Labs +// +// 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::cmp::min; +use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::sync::{Arc, LazyLock}; +use std::time::{Instant, SystemTime}; -use function_name::named; +use anyhow::Context; +use fail::fail_point; use futures::future::Shared; +use futures::stream::FuturesUnordered; +use futures::{FutureExt, StreamExt}; use itertools::Itertools; -use risingwave_hummock_sdk::{CompactionGroupId, HummockCompactionTaskId}; +use parking_lot::Mutex; +use rand::seq::SliceRandom; +use rand::thread_rng; +use risingwave_common::util::epoch::Epoch; +use risingwave_hummock_sdk::compaction_group::hummock_version_ext::HummockLevelsExt; +use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId; +use risingwave_hummock_sdk::table_stats::{ + add_prost_table_stats_map, purge_prost_table_stats, PbTableStatsMap, +}; +use risingwave_hummock_sdk::version::HummockVersion; +use risingwave_hummock_sdk::{ + compact_task_to_string, statistics_compact_task, CompactionGroupId, HummockCompactionTaskId, + HummockVersionId, +}; use risingwave_pb::hummock::compact_task::{TaskStatus, TaskType}; +use risingwave_pb::hummock::group_delta::DeltaType; use risingwave_pb::hummock::hummock_version::Levels; use risingwave_pb::hummock::subscribe_compaction_event_request::{ - self, Event as RequestEvent, PullTask, + self, Event as RequestEvent, HeartBeat, PullTask, ReportTask, }; use risingwave_pb::hummock::subscribe_compaction_event_response::{ Event as ResponseEvent, PullTaskAck, }; use risingwave_pb::hummock::{ - CompactStatus as PbCompactStatus, CompactTaskAssignment, CompactionConfig, + compact_task, CompactStatus as PbCompactStatus, CompactTask, CompactTaskAssignment, + CompactionConfig, GroupDelta, InputLevel, IntraLevelDelta, Level, SstableInfo, + StateTableInfoDelta, SubscribeCompactionEventRequest, TableOption, TableSchema, }; +use rw_futures_util::pending_on_none; use thiserror_ext::AsReport; -use tokio::sync::mpsc::UnboundedReceiver; -use tokio::sync::oneshot::Receiver as OneShotReceiver; +use tokio::sync::mpsc::error::SendError; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; +use tokio::sync::oneshot::{Receiver as OneShotReceiver, Sender}; +use tokio::task::JoinHandle; +use tonic::Streaming; +use tracing::warn; use crate::hummock::compaction::selector::level_selector::PickerInfo; -use crate::hummock::compaction::selector::DynamicLevelSelectorCore; +use crate::hummock::compaction::selector::{ + DynamicLevelSelector, DynamicLevelSelectorCore, LocalSelectorStatistic, ManualCompactionOption, + ManualCompactionSelector, SpaceReclaimCompactionSelector, TombstoneCompactionSelector, + TtlCompactionSelector, +}; use crate::hummock::compaction::{CompactStatus, CompactionDeveloperConfig, CompactionSelector}; -use crate::hummock::manager::{init_selectors, read_lock}; -use crate::hummock::HummockManager; +use crate::hummock::error::{Error, Result}; +use crate::hummock::manager::transaction::{ + HummockVersionStatsTransaction, HummockVersionTransaction, +}; +use crate::hummock::manager::versioning::Versioning; +use crate::hummock::metrics_utils::{ + build_compact_task_level_type_metrics_label, trigger_local_table_stat, trigger_sst_stat, +}; +use crate::hummock::sequence::next_compaction_task_id; +use crate::hummock::{commit_multi_var, start_measure_real_process_timer, HummockManager}; +use crate::manager::{MetadataManager, META_NODE_ID}; +use crate::model::BTreeMapTransaction; const MAX_SKIP_TIMES: usize = 8; const MAX_REPORT_COUNT: usize = 16; +static CANCEL_STATUS_SET: LazyLock> = LazyLock::new(|| { + [ + TaskStatus::ManualCanceled, + TaskStatus::SendFailCanceled, + TaskStatus::AssignFailCanceled, + TaskStatus::HeartbeatCanceled, + TaskStatus::InvalidGroupCanceled, + TaskStatus::NoAvailMemoryResourceCanceled, + TaskStatus::NoAvailCpuResourceCanceled, + TaskStatus::HeartbeatProgressCanceled, + ] + .into_iter() + .collect() +}); + +type CompactionRequestChannelItem = (CompactionGroupId, compact_task::TaskType); + +fn init_selectors() -> HashMap> { + let mut compaction_selectors: HashMap> = + HashMap::default(); + compaction_selectors.insert( + compact_task::TaskType::Dynamic, + Box::::default(), + ); + compaction_selectors.insert( + compact_task::TaskType::SpaceReclaim, + Box::::default(), + ); + compaction_selectors.insert( + compact_task::TaskType::Ttl, + Box::::default(), + ); + compaction_selectors.insert( + compact_task::TaskType::Tombstone, + Box::::default(), + ); + compaction_selectors +} + +impl<'a> HummockVersionTransaction<'a> { + fn apply_compact_task(&mut self, compact_task: &CompactTask) { + let mut version_delta = self.new_delta(); + let trivial_move = CompactStatus::is_trivial_move_task(compact_task); + version_delta.trivial_move = trivial_move; + + let group_deltas = &mut version_delta + .group_deltas + .entry(compact_task.compaction_group_id) + .or_default() + .group_deltas; + let mut removed_table_ids_map: BTreeMap> = BTreeMap::default(); + + for level in &compact_task.input_ssts { + let level_idx = level.level_idx; + let mut removed_table_ids = level + .table_infos + .iter() + .map(|sst| sst.get_sst_id()) + .collect_vec(); + + removed_table_ids_map + .entry(level_idx) + .or_default() + .append(&mut removed_table_ids); + } + + for (level_idx, removed_table_ids) in removed_table_ids_map { + let group_delta = GroupDelta { + delta_type: Some(DeltaType::IntraLevel(IntraLevelDelta { + level_idx, + removed_table_ids, + ..Default::default() + })), + }; + group_deltas.push(group_delta); + } + + let group_delta = GroupDelta { + delta_type: Some(DeltaType::IntraLevel(IntraLevelDelta { + level_idx: compact_task.target_level, + inserted_table_infos: compact_task.sorted_output_ssts.clone(), + l0_sub_level_id: compact_task.target_sub_level_id, + vnode_partition_count: compact_task.split_weight_by_vnode, + ..Default::default() + })), + }; + group_deltas.push(group_delta); + version_delta.safe_epoch = std::cmp::max( + version_delta.latest_version().visible_table_safe_epoch(), + compact_task.watermark, + ); + if version_delta.latest_version().visible_table_safe_epoch() < version_delta.safe_epoch { + version_delta.with_latest_version(|version, version_delta| { + for (table_id, info) in version.state_table_info.info() { + let new_safe_epoch = min(version_delta.safe_epoch, info.committed_epoch); + if new_safe_epoch > info.safe_epoch { + if new_safe_epoch != version_delta.safe_epoch { + warn!( + new_safe_epoch, + committed_epoch = info.committed_epoch, + global_safe_epoch = version_delta.safe_epoch, + table_id = table_id.table_id, + "table has different safe epoch to global" + ); + } + version_delta.state_table_info_delta.insert( + *table_id, + StateTableInfoDelta { + committed_epoch: info.committed_epoch, + safe_epoch: new_safe_epoch, + compaction_group_id: info.compaction_group_id, + }, + ); + } + } + }); + } + version_delta.pre_apply(); + } +} + #[derive(Default)] pub struct Compaction { /// Compaction task that is already assigned to a compactor @@ -50,38 +225,18 @@ pub struct Compaction { /// `CompactStatus` of each compaction group pub compaction_statuses: BTreeMap, - pub deterministic_mode: bool, + pub _deterministic_mode: bool, } impl HummockManager { - #[named] pub async fn get_assigned_compact_task_num(&self) -> u64 { - read_lock!(self, compaction) - .await - .compact_task_assignment - .len() as u64 - } - - #[named] - pub async fn list_all_tasks_ids(&self) -> Vec { - let compaction = read_lock!(self, compaction).await; - - compaction - .compaction_statuses - .iter() - .flat_map(|(_, cs)| { - cs.level_handlers - .iter() - .flat_map(|lh| lh.pending_tasks_ids()) - }) - .collect_vec() + self.compaction.read().await.compact_task_assignment.len() as u64 } - #[named] pub async fn list_compaction_status( &self, ) -> (Vec, Vec) { - let compaction = read_lock!(self, compaction).await; + let compaction = self.compaction.read().await; ( compaction.compaction_statuses.values().map_into().collect(), compaction @@ -92,14 +247,13 @@ impl HummockManager { ) } - #[named] pub async fn get_compaction_scores( &self, compaction_group_id: CompactionGroupId, ) -> Vec { let (status, levels, group) = { - let compaction = read_lock!(self, compaction).await; - let versioning = read_lock!(self, versioning).await; + let compaction = self.compaction.read().await; + let versioning = self.versioning.read().await; let config_manager = self.compaction_group_manager.read().await; match ( compaction.compaction_statuses.get(&compaction_group_id), @@ -119,12 +273,15 @@ impl HummockManager { let ctx = dynamic_level_core.get_priority_levels(&levels, &status.level_handlers); ctx.score_levels } +} - pub async fn handle_pull_task_event( +impl HummockManager { + async fn handle_pull_task_event( &self, context_id: u32, pull_task_count: usize, compaction_selectors: &mut HashMap>, + max_get_task_probe_times: usize, ) { assert_ne!(0, pull_task_count); if let Some(compactor) = self.compactor_manager.get_compactor(context_id) { @@ -137,8 +294,13 @@ impl HummockManager { let mut existed_groups = groups.clone(); let mut no_task_groups: HashSet = HashSet::default(); let mut failed_tasks = vec![]; + let mut loop_times = 0; - while generated_task_count < pull_task_count && failed_tasks.is_empty() { + while generated_task_count < pull_task_count + && failed_tasks.is_empty() + && loop_times < max_get_task_probe_times + { + loop_times += 1; let compact_ret = self .get_compact_tasks( existed_groups.clone(), @@ -149,11 +311,11 @@ impl HummockManager { match compact_ret { Ok((compact_tasks, unschedule_groups)) => { + no_task_groups.extend(unschedule_groups); if compact_tasks.is_empty() { break; } generated_task_count += compact_tasks.len(); - no_task_groups.extend(unschedule_groups); for task in compact_tasks { let task_id = task.task_id; if let Err(e) = @@ -203,7 +365,219 @@ impl HummockManager { } /// dedicated event runtime for CPU/IO bound event - pub async fn compact_task_dedicated_event_handler( + pub fn compaction_event_loop( + hummock_manager: Arc, + mut compactor_streams_change_rx: UnboundedReceiver<( + u32, + Streaming, + )>, + ) -> Vec<(JoinHandle<()>, Sender<()>)> { + let mut compactor_request_streams = FuturesUnordered::new(); + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + let (shutdown_tx_dedicated, shutdown_rx_dedicated) = tokio::sync::oneshot::channel(); + let shutdown_rx_shared = shutdown_rx.shared(); + let shutdown_rx_dedicated_shared = shutdown_rx_dedicated.shared(); + + let (tx, rx) = unbounded_channel(); + + let mut join_handle_vec = Vec::default(); + + let hummock_manager_dedicated = hummock_manager.clone(); + let compact_task_event_handler_join_handle = tokio::spawn(async move { + Self::compact_task_dedicated_event_handler( + hummock_manager_dedicated, + rx, + shutdown_rx_dedicated_shared, + ) + .await; + }); + + join_handle_vec.push(( + compact_task_event_handler_join_handle, + shutdown_tx_dedicated, + )); + + let join_handle = tokio::spawn(async move { + let push_stream = + |context_id: u32, + stream: Streaming, + compactor_request_streams: &mut FuturesUnordered<_>| { + let future = stream + .into_future() + .map(move |stream_future| (context_id, stream_future)); + + compactor_request_streams.push(future); + }; + + let mut event_loop_iteration_now = Instant::now(); + + loop { + let shutdown_rx_shared = shutdown_rx_shared.clone(); + let hummock_manager = hummock_manager.clone(); + hummock_manager + .metrics + .compaction_event_loop_iteration_latency + .observe(event_loop_iteration_now.elapsed().as_millis() as _); + event_loop_iteration_now = Instant::now(); + + tokio::select! { + _ = shutdown_rx_shared => { return; }, + + compactor_stream = compactor_streams_change_rx.recv() => { + if let Some((context_id, stream)) = compactor_stream { + tracing::info!("compactor {} enters the cluster", context_id); + push_stream(context_id, stream, &mut compactor_request_streams); + } + }, + + result = pending_on_none(compactor_request_streams.next()) => { + let mut compactor_alive = true; + + let (context_id, compactor_stream_req): (_, (std::option::Option>, _)) = result; + let (event, create_at, stream) = match compactor_stream_req { + (Some(Ok(req)), stream) => { + (req.event.unwrap(), req.create_at, stream) + } + + (Some(Err(err)), _stream) => { + tracing::warn!(error = %err.as_report(), "compactor stream {} poll with err, recv stream may be destroyed", context_id); + continue + } + + _ => { + tracing::warn!("compactor stream {} poll err, recv stream may be destroyed", context_id); + continue + }, + }; + + { + let consumed_latency_ms = SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Clock may have gone backwards") + .as_millis() + as u64 + - create_at; + hummock_manager.metrics + .compaction_event_consumed_latency + .observe(consumed_latency_ms as _); + } + + match event { + RequestEvent::HeartBeat(HeartBeat { + progress, + }) => { + let compactor_manager = hummock_manager.compactor_manager.clone(); + let cancel_tasks = compactor_manager.update_task_heartbeats(&progress).into_iter().map(|task|task.task_id).collect::>(); + if !cancel_tasks.is_empty() { + tracing::info!( + ?cancel_tasks, + context_id, + "Tasks cancel has expired due to lack of visible progress", + ); + + if let Err(e) = hummock_manager + .cancel_compact_tasks(cancel_tasks.clone(), TaskStatus::HeartbeatProgressCanceled) + .await + { + tracing::error!( + error = %e.as_report(), + "Attempt to remove compaction task due to elapsed heartbeat failed. We will continue to track its heartbeat + until we can successfully report its status." + ); + } + } + + if let Some(compactor) = compactor_manager.get_compactor(context_id) { + // Forcefully cancel the task so that it terminates + // early on the compactor + // node. + if !cancel_tasks.is_empty() { + let _ = compactor.cancel_tasks(&cancel_tasks); + tracing::info!( + ?cancel_tasks, + context_id, + "CancelTask operation has been sent to compactor node", + ); + } + } else { + // Determine the validity of the compactor streaming rpc. When the compactor no longer exists in the manager, the stream will be removed. + // Tip: Connectivity to the compactor will be determined through the `send_event` operation. When send fails, it will be removed from the manager + compactor_alive = false; + } + }, + + RequestEvent::Register(_) => { + unreachable!() + } + + e @ (RequestEvent::PullTask(_) | RequestEvent::ReportTask(_)) => { + let _ = tx.send((context_id, e)); + } + } + + if compactor_alive { + push_stream(context_id, stream, &mut compactor_request_streams); + } else { + tracing::warn!(context_id, "compactor stream error, send stream may be destroyed"); + } + }, + } + } + }); + + join_handle_vec.push((join_handle, shutdown_tx)); + + join_handle_vec + } + + pub fn add_compactor_stream( + &self, + context_id: u32, + req_stream: Streaming, + ) { + self.compactor_streams_change_tx + .send((context_id, req_stream)) + .unwrap(); + } + + pub async fn auto_pick_compaction_group_and_type( + &self, + ) -> Option<(CompactionGroupId, compact_task::TaskType)> { + let mut compaction_group_ids = self.compaction_group_ids().await; + compaction_group_ids.shuffle(&mut thread_rng()); + + for cg_id in compaction_group_ids { + if let Some(pick_type) = self.compaction_state.auto_pick_type(cg_id) { + return Some((cg_id, pick_type)); + } + } + + None + } + + /// This method will return all compaction group id in a random order and task type. If there are any group block by `write_limit`, it will return a single array with `TaskType::Emergency`. + /// If these groups get different task-type, it will return all group id with `TaskType::Dynamic` if the first group get `TaskType::Dynamic`, otherwise it will return the single group with other task type. + async fn auto_pick_compaction_groups_and_type( + &self, + ) -> (Vec, compact_task::TaskType) { + let mut compaction_group_ids = self.compaction_group_ids().await; + compaction_group_ids.shuffle(&mut thread_rng()); + + let mut normal_groups = vec![]; + for cg_id in compaction_group_ids { + if let Some(pick_type) = self.compaction_state.auto_pick_type(cg_id) { + if pick_type == TaskType::Dynamic { + normal_groups.push(cg_id); + } else if normal_groups.is_empty() { + return (vec![cg_id], pick_type); + } + } + } + (normal_groups, TaskType::Dynamic) + } + + /// dedicated event runtime for CPU/IO bound event + async fn compact_task_dedicated_event_handler( hummock_manager: Arc, mut rx: UnboundedReceiver<(u32, subscribe_compaction_event_request::Event)>, shutdown_rx_shared: Shared>, @@ -219,7 +593,7 @@ impl HummockManager { let mut skip_times = 0; match event { RequestEvent::PullTask(PullTask { pull_task_count }) => { - hummock_manager.handle_pull_task_event(context_id, pull_task_count as usize, &mut compaction_selectors).await; + hummock_manager.handle_pull_task_event(context_id, pull_task_count as usize, &mut compaction_selectors, hummock_manager.env.opts.max_get_task_probe_times).await; } RequestEvent::ReportTask(task) => { @@ -231,7 +605,7 @@ impl HummockManager { while let Ok((context_id, event)) = rx.try_recv() { match event { RequestEvent::PullTask(PullTask { pull_task_count }) => { - hummock_manager.handle_pull_task_event(context_id, pull_task_count as usize, &mut compaction_selectors).await; + hummock_manager.handle_pull_task_event(context_id, pull_task_count as usize, &mut compaction_selectors, hummock_manager.env.opts.max_get_task_probe_times).await; if !report_events.is_empty() { if skip_times > MAX_SKIP_TIMES { break; @@ -261,6 +635,1054 @@ impl HummockManager { } } +impl HummockManager { + pub async fn get_compact_tasks_impl( + &self, + compaction_groups: Vec, + max_select_count: usize, + selector: &mut Box, + ) -> Result<(Vec, Vec)> { + // TODO: `get_all_table_options` will hold catalog_manager async lock, to avoid holding the + // lock in compaction_guard, take out all table_options in advance there may be a + // waste of resources here, need to add a more efficient filter in catalog_manager + let deterministic_mode = self.env.opts.compaction_deterministic_test; + let all_table_id_to_option = self + .metadata_manager + .get_all_table_options() + .await + .map_err(|err| Error::MetaStore(err.into()))?; + + let mut compaction_guard = self.compaction.write().await; + let compaction: &mut Compaction = &mut compaction_guard; + let mut versioning_guard = self.versioning.write().await; + let versioning: &mut Versioning = &mut versioning_guard; + + let _timer = start_measure_real_process_timer!(self, "get_compact_tasks_impl"); + + let start_time = Instant::now(); + let max_committed_epoch = versioning.current_version.max_committed_epoch; + let watermark = self + .context_info + .read() + .await + .pinned_snapshots + .values() + .map(|v| v.minimal_pinned_snapshot) + .fold(max_committed_epoch, std::cmp::min); + + let mut compaction_statuses = BTreeMapTransaction::new(&mut compaction.compaction_statuses); + + let mut compact_task_assignment = + BTreeMapTransaction::new(&mut compaction.compact_task_assignment); + + let mut version = HummockVersionTransaction::new( + &mut versioning.current_version, + &mut versioning.hummock_version_deltas, + self.env.notification_manager(), + &self.metrics, + ); + + if deterministic_mode { + version.disable_apply_to_txn(); + } + + let mut unschedule_groups = vec![]; + let mut trivial_tasks = vec![]; + let mut pick_tasks = vec![]; + let developer_config = Arc::new(CompactionDeveloperConfig::new_from_meta_opts( + &self.env.opts, + )); + 'outside: for compaction_group_id in compaction_groups { + if pick_tasks.len() >= max_select_count { + break; + } + + if !version + .latest_version() + .levels + .contains_key(&compaction_group_id) + { + continue; + } + + // When the last table of a compaction group is deleted, the compaction group (and its + // config) is destroyed as well. Then a compaction task for this group may come later and + // cannot find its config. + let group_config = { + let config_manager = self.compaction_group_manager.read().await; + + match config_manager.try_get_compaction_group_config(compaction_group_id) { + Some(config) => config, + None => continue, + } + }; + + // StoredIdGenerator already implements ids pre-allocation by ID_PREALLOCATE_INTERVAL. + let task_id = next_compaction_task_id(&self.env).await?; + + if !compaction_statuses.contains_key(&compaction_group_id) { + // lazy initialize. + compaction_statuses.insert( + compaction_group_id, + CompactStatus::new( + compaction_group_id, + group_config.compaction_config.max_level, + ), + ); + } + let mut compact_status = compaction_statuses.get_mut(compaction_group_id).unwrap(); + + let can_trivial_move = matches!(selector.task_type(), TaskType::Dynamic) + || matches!(selector.task_type(), TaskType::Emergency); + + let mut stats = LocalSelectorStatistic::default(); + let member_table_ids: Vec<_> = version + .latest_version() + .state_table_info + .compaction_group_member_table_ids(compaction_group_id) + .iter() + .map(|table_id| table_id.table_id) + .collect(); + + let mut table_id_to_option: HashMap = HashMap::default(); + + for table_id in &member_table_ids { + if let Some(opts) = all_table_id_to_option.get(table_id) { + table_id_to_option.insert(*table_id, *opts); + } + } + + while let Some(compact_task) = compact_status.get_compact_task( + version + .latest_version() + .get_compaction_group_levels(compaction_group_id), + version + .latest_version() + .state_table_info + .compaction_group_member_table_ids(compaction_group_id), + task_id as HummockCompactionTaskId, + &group_config, + &mut stats, + selector, + table_id_to_option.clone(), + developer_config.clone(), + ) { + let target_level_id = compact_task.input.target_level as u32; + + let compression_algorithm = match compact_task.compression_algorithm.as_str() { + "Lz4" => 1, + "Zstd" => 2, + _ => 0, + }; + let vnode_partition_count = compact_task.input.vnode_partition_count; + use risingwave_hummock_sdk::prost_key_range::KeyRangeExt; + + let mut compact_task = CompactTask { + input_ssts: compact_task.input.input_levels, + splits: vec![risingwave_pb::hummock::KeyRange::inf()], + watermark, + sorted_output_ssts: vec![], + task_id, + target_level: target_level_id, + // only gc delete keys in last level because there may be older version in more bottom + // level. + gc_delete_keys: version + .latest_version() + .get_compaction_group_levels(compaction_group_id) + .is_last_level(target_level_id), + base_level: compact_task.base_level as u32, + task_status: TaskStatus::Pending as i32, + compaction_group_id: group_config.group_id, + existing_table_ids: member_table_ids.clone(), + compression_algorithm, + target_file_size: compact_task.target_file_size, + table_options: table_id_to_option + .iter() + .map(|(table_id, table_option)| { + (*table_id, TableOption::from(table_option)) + }) + .collect(), + current_epoch_time: Epoch::now().0, + compaction_filter_mask: group_config.compaction_config.compaction_filter_mask, + target_sub_level_id: compact_task.input.target_sub_level_id, + task_type: compact_task.compaction_task_type as i32, + split_weight_by_vnode: vnode_partition_count, + max_sub_compaction: group_config.compaction_config.max_sub_compaction, + ..Default::default() + }; + + let is_trivial_reclaim = CompactStatus::is_trivial_reclaim(&compact_task); + let is_trivial_move = CompactStatus::is_trivial_move_task(&compact_task); + if is_trivial_reclaim || (is_trivial_move && can_trivial_move) { + let log_label = if is_trivial_reclaim { + "TrivialReclaim" + } else { + "TrivialMove" + }; + let label = if is_trivial_reclaim { + "trivial-space-reclaim" + } else { + "trivial-move" + }; + + tracing::debug!( + "{} for compaction group {}: input: {:?}, cost time: {:?}", + log_label, + compact_task.compaction_group_id, + compact_task.input_ssts, + start_time.elapsed() + ); + compact_task.set_task_status(TaskStatus::Success); + compact_status.report_compact_task(&compact_task); + if !is_trivial_reclaim { + compact_task + .sorted_output_ssts + .clone_from(&compact_task.input_ssts[0].table_infos); + } + self.metrics + .compact_frequency + .with_label_values(&[ + label, + &compact_task.compaction_group_id.to_string(), + selector.task_type().as_str_name(), + "SUCCESS", + ]) + .inc(); + + version.apply_compact_task(&compact_task); + trivial_tasks.push(compact_task); + if trivial_tasks.len() >= self.env.opts.max_trivial_move_task_count_per_loop { + break 'outside; + } + } else { + self.calculate_vnode_partition( + &mut compact_task, + group_config.compaction_config.as_ref(), + ) + .await; + compact_task.table_watermarks = version + .latest_version() + .safe_epoch_table_watermarks(&compact_task.existing_table_ids); + + if self.env.opts.enable_dropped_column_reclaim { + // TODO: get all table schemas for all tables in once call to avoid acquiring lock and await. + compact_task.table_schemas = match self.metadata_manager() { + MetadataManager::V1(mgr) => mgr + .catalog_manager + .get_versioned_table_schemas(&compact_task.existing_table_ids) + .await + .into_iter() + .map(|(table_id, column_ids)| { + (table_id, TableSchema { column_ids }) + }) + .collect(), + MetadataManager::V2(_) => { + // TODO #13952: support V2 + BTreeMap::default() + } + }; + } + + compact_task_assignment.insert( + compact_task.task_id, + CompactTaskAssignment { + compact_task: Some(compact_task.clone()), + context_id: META_NODE_ID, // deprecated + }, + ); + + pick_tasks.push(compact_task); + break; + } + + stats.report_to_metrics(compaction_group_id, self.metrics.as_ref()); + stats = LocalSelectorStatistic::default(); + } + if pick_tasks + .last() + .map(|task| task.compaction_group_id != compaction_group_id) + .unwrap_or(true) + { + unschedule_groups.push(compaction_group_id); + } + stats.report_to_metrics(compaction_group_id, self.metrics.as_ref()); + } + + if !trivial_tasks.is_empty() { + commit_multi_var!( + self.meta_store_ref(), + compaction_statuses, + compact_task_assignment, + version + )?; + self.metrics + .compact_task_batch_count + .with_label_values(&["batch_trivial_move"]) + .observe(trivial_tasks.len() as f64); + drop(versioning_guard); + } else { + // We are using a single transaction to ensure that each task has progress when it is + // created. + drop(versioning_guard); + commit_multi_var!( + self.meta_store_ref(), + compaction_statuses, + compact_task_assignment + )?; + } + drop(compaction_guard); + if !pick_tasks.is_empty() { + self.metrics + .compact_task_batch_count + .with_label_values(&["batch_get_compact_task"]) + .observe(pick_tasks.len() as f64); + } + + for compact_task in &mut pick_tasks { + let compaction_group_id = compact_task.compaction_group_id; + + // Initiate heartbeat for the task to track its progress. + self.compactor_manager + .initiate_task_heartbeat(compact_task.clone()); + + // this task has been finished. + compact_task.set_task_status(TaskStatus::Pending); + let compact_task_statistics = statistics_compact_task(compact_task); + + let level_type_label = build_compact_task_level_type_metrics_label( + compact_task.input_ssts[0].level_idx as usize, + compact_task.input_ssts.last().unwrap().level_idx as usize, + ); + + let level_count = compact_task.input_ssts.len(); + if compact_task.input_ssts[0].level_idx == 0 { + self.metrics + .l0_compact_level_count + .with_label_values(&[&compaction_group_id.to_string(), &level_type_label]) + .observe(level_count as _); + } + + self.metrics + .compact_task_size + .with_label_values(&[&compaction_group_id.to_string(), &level_type_label]) + .observe(compact_task_statistics.total_file_size as _); + + self.metrics + .compact_task_size + .with_label_values(&[ + &compaction_group_id.to_string(), + &format!("{} uncompressed", level_type_label), + ]) + .observe(compact_task_statistics.total_uncompressed_file_size as _); + + self.metrics + .compact_task_file_count + .with_label_values(&[&compaction_group_id.to_string(), &level_type_label]) + .observe(compact_task_statistics.total_file_count as _); + + tracing::trace!( + "For compaction group {}: pick up {} {} sub_level in level {} to compact to target {}. cost time: {:?} compact_task_statistics {:?}", + compaction_group_id, + level_count, + compact_task.input_ssts[0].level_type().as_str_name(), + compact_task.input_ssts[0].level_idx, + compact_task.target_level, + start_time.elapsed(), + compact_task_statistics + ); + } + + #[cfg(test)] + { + self.check_state_consistency().await; + } + pick_tasks.extend(trivial_tasks); + Ok((pick_tasks, unschedule_groups)) + } + + /// Cancels a compaction task no matter it's assigned or unassigned. + pub async fn cancel_compact_task(&self, task_id: u64, task_status: TaskStatus) -> Result { + fail_point!("fp_cancel_compact_task", |_| Err(Error::MetaStore( + anyhow::anyhow!("failpoint metastore err") + ))); + let ret = self + .cancel_compact_task_impl(vec![task_id], task_status) + .await?; + Ok(ret[0]) + } + + pub async fn cancel_compact_tasks( + &self, + tasks: Vec, + task_status: TaskStatus, + ) -> Result> { + self.cancel_compact_task_impl(tasks, task_status).await + } + + async fn cancel_compact_task_impl( + &self, + task_ids: Vec, + task_status: TaskStatus, + ) -> Result> { + assert!(CANCEL_STATUS_SET.contains(&task_status)); + let tasks = task_ids + .into_iter() + .map(|task_id| ReportTask { + task_id, + task_status: task_status as i32, + sorted_output_ssts: vec![], + table_stats_change: HashMap::default(), + }) + .collect_vec(); + let rets = self.report_compact_tasks(tasks).await?; + #[cfg(test)] + { + self.check_state_consistency().await; + } + Ok(rets) + } + + async fn get_compact_tasks( + &self, + mut compaction_groups: Vec, + max_select_count: usize, + selector: &mut Box, + ) -> Result<(Vec, Vec)> { + fail_point!("fp_get_compact_task", |_| Err(Error::MetaStore( + anyhow::anyhow!("failpoint metastore error") + ))); + compaction_groups.shuffle(&mut thread_rng()); + let (mut tasks, groups) = self + .get_compact_tasks_impl(compaction_groups, max_select_count, selector) + .await?; + tasks.retain(|task| { + if task.task_status() == TaskStatus::Success { + debug_assert!( + CompactStatus::is_trivial_reclaim(task) + || CompactStatus::is_trivial_move_task(task) + ); + false + } else { + true + } + }); + Ok((tasks, groups)) + } + + pub async fn get_compact_task( + &self, + compaction_group_id: CompactionGroupId, + selector: &mut Box, + ) -> Result> { + fail_point!("fp_get_compact_task", |_| Err(Error::MetaStore( + anyhow::anyhow!("failpoint metastore error") + ))); + + let (normal_tasks, _) = self + .get_compact_tasks_impl(vec![compaction_group_id], 1, selector) + .await?; + for task in normal_tasks { + if task.task_status() != TaskStatus::Success { + return Ok(Some(task)); + } + debug_assert!( + CompactStatus::is_trivial_reclaim(&task) + || CompactStatus::is_trivial_move_task(&task) + ); + } + Ok(None) + } + + pub async fn manual_get_compact_task( + &self, + compaction_group_id: CompactionGroupId, + manual_compaction_option: ManualCompactionOption, + ) -> Result> { + let mut selector: Box = + Box::new(ManualCompactionSelector::new(manual_compaction_option)); + self.get_compact_task(compaction_group_id, &mut selector) + .await + } + + pub(super) fn is_compact_task_expired( + compact_task: &CompactTask, + hummock_version: &HummockVersion, + ) -> bool { + if let Some(group) = hummock_version + .levels + .get(&compact_task.compaction_group_id) + { + for input_level in compact_task.get_input_ssts() { + let input_level: &InputLevel = input_level; + let mut sst_ids: HashSet<_> = input_level + .table_infos + .iter() + .map(|sst| sst.sst_id) + .collect(); + fn filter_ssts(levels: &Level, sst_ids: &mut HashSet) { + for sst in &levels.table_infos { + sst_ids.remove(&sst.sst_id); + } + } + if input_level.level_idx == 0 { + for level in &group.get_level0().sub_levels { + filter_ssts(level, &mut sst_ids); + } + } else { + filter_ssts(group.get_level(input_level.level_idx as _), &mut sst_ids); + } + if !sst_ids.is_empty() { + warn!(stale_sst_id = ?sst_ids, ?compact_task, "compact task expired"); + return true; + } + } + } + false + } + + pub async fn report_compact_task( + &self, + task_id: u64, + task_status: TaskStatus, + sorted_output_ssts: Vec, + table_stats_change: Option, + ) -> Result { + let rets = self + .report_compact_tasks(vec![ReportTask { + task_id, + task_status: task_status as i32, + sorted_output_ssts, + table_stats_change: table_stats_change.unwrap_or_default(), + }]) + .await?; + Ok(rets[0]) + } + + /// Finishes or cancels a compaction task, according to `task_status`. + /// + /// If `context_id` is not None, its validity will be checked when writing meta store. + /// Its ownership of the task is checked as well. + /// + /// Return Ok(false) indicates either the task is not found, + /// or the task is not owned by `context_id` when `context_id` is not None. + + pub async fn report_compact_tasks(&self, report_tasks: Vec) -> Result> { + let mut guard = self.compaction.write().await; + let deterministic_mode = self.env.opts.compaction_deterministic_test; + let compaction: &mut Compaction = &mut guard; + let start_time = Instant::now(); + let original_keys = compaction.compaction_statuses.keys().cloned().collect_vec(); + let mut compact_statuses = BTreeMapTransaction::new(&mut compaction.compaction_statuses); + let mut rets = vec![false; report_tasks.len()]; + let mut compact_task_assignment = + BTreeMapTransaction::new(&mut compaction.compact_task_assignment); + // The compaction task is finished. + let mut versioning_guard = self.versioning.write().await; + let versioning: &mut Versioning = &mut versioning_guard; + let _timer = start_measure_real_process_timer!(self, "report_compact_tasks"); + + // purge stale compact_status + for group_id in original_keys { + if !versioning.current_version.levels.contains_key(&group_id) { + compact_statuses.remove(group_id); + } + } + let mut tasks = vec![]; + + let mut version = HummockVersionTransaction::new( + &mut versioning.current_version, + &mut versioning.hummock_version_deltas, + self.env.notification_manager(), + &self.metrics, + ); + + if deterministic_mode { + version.disable_apply_to_txn(); + } + + let mut version_stats = HummockVersionStatsTransaction::new( + &mut versioning.version_stats, + self.env.notification_manager(), + ); + let mut success_count = 0; + for (idx, task) in report_tasks.into_iter().enumerate() { + rets[idx] = true; + let mut compact_task = match compact_task_assignment.remove(task.task_id) { + Some(compact_task) => compact_task.compact_task.unwrap(), + None => { + tracing::warn!("{}", format!("compact task {} not found", task.task_id)); + rets[idx] = false; + continue; + } + }; + + { + // apply result + compact_task.task_status = task.task_status; + compact_task.sorted_output_ssts = task.sorted_output_ssts; + } + + match compact_statuses.get_mut(compact_task.compaction_group_id) { + Some(mut compact_status) => { + compact_status.report_compact_task(&compact_task); + } + None => { + compact_task.set_task_status(TaskStatus::InvalidGroupCanceled); + } + } + + let input_sst_ids: HashSet = compact_task + .input_ssts + .iter() + .flat_map(|level| level.table_infos.iter().map(|sst| sst.sst_id)) + .collect(); + let input_level_ids: Vec = compact_task + .input_ssts + .iter() + .map(|level| level.level_idx) + .collect(); + let is_success = if let TaskStatus::Success = compact_task.task_status() { + // if member_table_ids changes, the data of sstable may stale. + let is_expired = + Self::is_compact_task_expired(&compact_task, version.latest_version()); + if is_expired { + compact_task.set_task_status(TaskStatus::InputOutdatedCanceled); + false + } else { + let group = version + .latest_version() + .levels + .get(&compact_task.compaction_group_id) + .unwrap(); + let input_exist = + group.check_deleted_sst_exist(&input_level_ids, input_sst_ids); + if !input_exist { + compact_task.set_task_status(TaskStatus::InvalidGroupCanceled); + warn!( + "The task may be expired because of group split, task:\n {:?}", + compact_task_to_string(&compact_task) + ); + } + input_exist + } + } else { + false + }; + if is_success { + success_count += 1; + version.apply_compact_task(&compact_task); + if purge_prost_table_stats(&mut version_stats.table_stats, version.latest_version()) + { + self.metrics.version_stats.reset(); + versioning.local_metrics.clear(); + } + add_prost_table_stats_map(&mut version_stats.table_stats, &task.table_stats_change); + trigger_local_table_stat( + &self.metrics, + &mut versioning.local_metrics, + &version_stats, + &task.table_stats_change, + ); + } + tasks.push(compact_task); + } + if success_count > 0 { + commit_multi_var!( + self.meta_store_ref(), + compact_statuses, + compact_task_assignment, + version, + version_stats + )?; + + self.metrics + .compact_task_batch_count + .with_label_values(&["batch_report_task"]) + .observe(success_count as f64); + } else { + // The compaction task is cancelled or failed. + commit_multi_var!( + self.meta_store_ref(), + compact_statuses, + compact_task_assignment + )?; + } + let mut success_groups = vec![]; + for compact_task in tasks { + let task_status = compact_task.task_status(); + let task_status_label = task_status.as_str_name(); + let task_type_label = compact_task.task_type().as_str_name(); + + self.compactor_manager + .remove_task_heartbeat(compact_task.task_id); + + self.metrics + .compact_frequency + .with_label_values(&[ + "normal", + &compact_task.compaction_group_id.to_string(), + task_type_label, + task_status_label, + ]) + .inc(); + + tracing::trace!( + "Reported compaction task. {}. cost time: {:?}", + compact_task_to_string(&compact_task), + start_time.elapsed(), + ); + + trigger_sst_stat( + &self.metrics, + compaction + .compaction_statuses + .get(&compact_task.compaction_group_id), + &versioning_guard.current_version, + compact_task.compaction_group_id, + ); + + if !deterministic_mode + && (matches!(compact_task.task_type(), compact_task::TaskType::Dynamic) + || matches!(compact_task.task_type(), compact_task::TaskType::Emergency)) + { + // only try send Dynamic compaction + self.try_send_compaction_request( + compact_task.compaction_group_id, + compact_task::TaskType::Dynamic, + ); + } + + if task_status == TaskStatus::Success { + success_groups.push(compact_task.compaction_group_id); + } + } + drop(versioning_guard); + if !success_groups.is_empty() { + self.try_update_write_limits(&success_groups).await; + } + Ok(rets) + } + + /// Triggers compacitons to specified compaction groups. + /// Don't wait for compaction finish + pub async fn trigger_compaction_deterministic( + &self, + _base_version_id: HummockVersionId, + compaction_groups: Vec, + ) -> Result<()> { + let old_version = self.get_current_version().await; + tracing::info!( + "Trigger compaction for version {}, epoch {}, groups {:?}", + old_version.id, + old_version.max_committed_epoch, + compaction_groups + ); + + if compaction_groups.is_empty() { + return Ok(()); + } + for compaction_group in compaction_groups { + self.try_send_compaction_request(compaction_group, compact_task::TaskType::Dynamic); + } + Ok(()) + } + + pub async fn trigger_manual_compaction( + &self, + compaction_group: CompactionGroupId, + manual_compaction_option: ManualCompactionOption, + ) -> Result<()> { + let start_time = Instant::now(); + + // 1. Get idle compactor. + let compactor = match self.compactor_manager.next_compactor() { + Some(compactor) => compactor, + None => { + tracing::warn!("trigger_manual_compaction No compactor is available."); + return Err(anyhow::anyhow!( + "trigger_manual_compaction No compactor is available. compaction_group {}", + compaction_group + ) + .into()); + } + }; + + // 2. Get manual compaction task. + let compact_task = self + .manual_get_compact_task(compaction_group, manual_compaction_option) + .await; + let compact_task = match compact_task { + Ok(Some(compact_task)) => compact_task, + Ok(None) => { + // No compaction task available. + return Err(anyhow::anyhow!( + "trigger_manual_compaction No compaction_task is available. compaction_group {}", + compaction_group + ) + .into()); + } + Err(err) => { + tracing::warn!(error = %err.as_report(), "Failed to get compaction task"); + + return Err(anyhow::anyhow!(err) + .context(format!( + "Failed to get compaction task for compaction_group {}", + compaction_group, + )) + .into()); + } + }; + + // 3. send task to compactor + let compact_task_string = compact_task_to_string(&compact_task); + // TODO: shall we need to cancel on meta ? + compactor + .send_event(ResponseEvent::CompactTask(compact_task)) + .with_context(|| { + format!( + "Failed to trigger compaction task for compaction_group {}", + compaction_group, + ) + })?; + + tracing::info!( + "Trigger manual compaction task. {}. cost time: {:?}", + &compact_task_string, + start_time.elapsed(), + ); + + Ok(()) + } + + /// Sends a compaction request. + pub fn try_send_compaction_request( + &self, + compaction_group: CompactionGroupId, + task_type: compact_task::TaskType, + ) -> bool { + match self + .compaction_state + .try_sched_compaction(compaction_group, task_type) + { + Ok(_) => true, + Err(e) => { + tracing::error!( + error = %e.as_report(), + "failed to send compaction request for compaction group {}", + compaction_group, + ); + false + } + } + } + + pub(crate) async fn calculate_vnode_partition( + &self, + compact_task: &mut CompactTask, + compaction_config: &CompactionConfig, + ) { + // do not split sst by vnode partition when target_level > base_level + // The purpose of data alignment is mainly to improve the parallelism of base level compaction and reduce write amplification. + // However, at high level, the size of the sst file is often larger and only contains the data of a single table_id, so there is no need to cut it. + if compact_task.target_level > compact_task.base_level { + return; + } + if compaction_config.split_weight_by_vnode > 0 { + for table_id in &compact_task.existing_table_ids { + compact_task + .table_vnode_partition + .insert(*table_id, compact_task.split_weight_by_vnode); + } + } else { + let mut table_size_info: HashMap = HashMap::default(); + let mut existing_table_ids: HashSet = HashSet::default(); + for input_ssts in &compact_task.input_ssts { + for sst in &input_ssts.table_infos { + existing_table_ids.extend(sst.table_ids.iter()); + for table_id in &sst.table_ids { + *table_size_info.entry(*table_id).or_default() += + sst.file_size / (sst.table_ids.len() as u64); + } + } + } + compact_task + .existing_table_ids + .retain(|table_id| existing_table_ids.contains(table_id)); + + let hybrid_vnode_count = self.env.opts.hybrid_partition_node_count; + let default_partition_count = self.env.opts.partition_vnode_count; + // We must ensure the partition threshold large enough to avoid too many small files. + let compact_task_table_size_partition_threshold_low = self + .env + .opts + .compact_task_table_size_partition_threshold_low; + let compact_task_table_size_partition_threshold_high = self + .env + .opts + .compact_task_table_size_partition_threshold_high; + use risingwave_common::system_param::reader::SystemParamsRead; + let params = self.env.system_params_reader().await; + let barrier_interval_ms = params.barrier_interval_ms() as u64; + let checkpoint_secs = std::cmp::max( + 1, + params.checkpoint_frequency() * barrier_interval_ms / 1000, + ); + // check latest write throughput + let history_table_throughput = self.history_table_throughput.read(); + for (table_id, compact_table_size) in table_size_info { + let write_throughput = history_table_throughput + .get(&table_id) + .map(|que| que.back().cloned().unwrap_or(0)) + .unwrap_or(0) + / checkpoint_secs; + if compact_table_size > compact_task_table_size_partition_threshold_high + && default_partition_count > 0 + { + compact_task + .table_vnode_partition + .insert(table_id, default_partition_count); + } else if (compact_table_size > compact_task_table_size_partition_threshold_low + || (write_throughput > self.env.opts.table_write_throughput_threshold + && compact_table_size > compaction_config.target_file_size_base)) + && hybrid_vnode_count > 0 + { + // partition for large write throughput table. But we also need to make sure that it can not be too small. + compact_task + .table_vnode_partition + .insert(table_id, hybrid_vnode_count); + } else if compact_table_size > compaction_config.target_file_size_base { + // partition for small table + compact_task.table_vnode_partition.insert(table_id, 1); + } + } + compact_task + .table_vnode_partition + .retain(|table_id, _| compact_task.existing_table_ids.contains(table_id)); + } + } + + pub async fn try_move_table_to_dedicated_cg( + &self, + table_write_throughput: &HashMap>, + table_id: &u32, + table_size: &u64, + is_creating_table: bool, + checkpoint_secs: u64, + parent_group_id: u64, + group_size: u64, + ) { + let default_group_id: CompactionGroupId = StaticCompactionGroupId::StateDefault.into(); + let mv_group_id: CompactionGroupId = StaticCompactionGroupId::MaterializedView.into(); + let partition_vnode_count = self.env.opts.partition_vnode_count; + let window_size = + self.env.opts.table_info_statistic_history_times / (checkpoint_secs as usize); + + let mut is_high_write_throughput = false; + let mut is_low_write_throughput = true; + if let Some(history) = table_write_throughput.get(table_id) { + if history.len() >= window_size { + is_high_write_throughput = history.iter().all(|throughput| { + *throughput / checkpoint_secs > self.env.opts.table_write_throughput_threshold + }); + is_low_write_throughput = history.iter().any(|throughput| { + *throughput / checkpoint_secs < self.env.opts.min_table_split_write_throughput + }); + } + } + + let state_table_size = *table_size; + + // 1. Avoid splitting a creating table + // 2. Avoid splitting a is_low_write_throughput creating table + // 3. Avoid splitting a non-high throughput medium-sized table + if is_creating_table + || (is_low_write_throughput) + || (state_table_size < self.env.opts.min_table_split_size && !is_high_write_throughput) + { + return; + } + + // do not split a large table and a small table because it would increase IOPS + // of small table. + if parent_group_id != default_group_id && parent_group_id != mv_group_id { + let rest_group_size = group_size - state_table_size; + if rest_group_size < state_table_size + && rest_group_size < self.env.opts.min_table_split_size + { + return; + } + } + + let ret = self + .move_state_table_to_compaction_group( + parent_group_id, + &[*table_id], + partition_vnode_count, + ) + .await; + match ret { + Ok(new_group_id) => { + tracing::info!("move state table [{}] from group-{} to group-{} success table_vnode_partition_count {:?}", table_id, parent_group_id, new_group_id, partition_vnode_count); + } + Err(e) => { + tracing::info!( + error = %e.as_report(), + "failed to move state table [{}] from group-{}", + table_id, + parent_group_id, + ) + } + } + } +} + +#[cfg(any(test, feature = "test"))] +impl HummockManager { + pub fn compactor_manager_ref_for_test(&self) -> crate::hummock::CompactorManagerRef { + self.compactor_manager.clone() + } + + pub async fn compaction_task_from_assignment_for_test( + &self, + task_id: u64, + ) -> Option { + let compaction_guard = self.compaction.read().await; + let assignment_ref = &compaction_guard.compact_task_assignment; + assignment_ref.get(&task_id).cloned() + } + + pub async fn report_compact_task_for_test( + &self, + task_id: u64, + compact_task: Option, + task_status: TaskStatus, + sorted_output_ssts: Vec, + table_stats_change: Option, + ) -> Result<()> { + if let Some(task) = compact_task { + let mut guard = self.compaction.write().await; + guard.compact_task_assignment.insert( + task_id, + CompactTaskAssignment { + compact_task: Some(task), + context_id: 0, + }, + ); + } + + // In the test, the contents of the compact task may have been modified directly, while the contents of compact_task_assignment were not modified. + // So we pass the modified compact_task directly into the `report_compact_task_impl` + self.report_compact_tasks(vec![ReportTask { + task_id, + task_status: task_status as i32, + sorted_output_ssts, + table_stats_change: table_stats_change.unwrap_or_default(), + }]) + .await?; + Ok(()) + } +} + pub fn check_cg_write_limit( levels: &Levels, compaction_config: &CompactionConfig, @@ -298,3 +1720,54 @@ impl WriteLimitType { matches!(self, Self::WriteStop(_, _)) } } + +#[derive(Debug, Default)] +pub struct CompactionState { + scheduled: Mutex>, +} + +impl CompactionState { + pub fn new() -> Self { + Self { + scheduled: Default::default(), + } + } + + /// Enqueues only if the target is not yet in queue. + pub fn try_sched_compaction( + &self, + compaction_group: CompactionGroupId, + task_type: TaskType, + ) -> std::result::Result> { + let mut guard = self.scheduled.lock(); + let key = (compaction_group, task_type); + if guard.contains(&key) { + return Ok(false); + } + guard.insert(key); + Ok(true) + } + + pub fn unschedule( + &self, + compaction_group: CompactionGroupId, + task_type: compact_task::TaskType, + ) { + self.scheduled.lock().remove(&(compaction_group, task_type)); + } + + pub fn auto_pick_type(&self, group: CompactionGroupId) -> Option { + let guard = self.scheduled.lock(); + if guard.contains(&(group, compact_task::TaskType::Dynamic)) { + Some(compact_task::TaskType::Dynamic) + } else if guard.contains(&(group, compact_task::TaskType::SpaceReclaim)) { + Some(compact_task::TaskType::SpaceReclaim) + } else if guard.contains(&(group, compact_task::TaskType::Ttl)) { + Some(compact_task::TaskType::Ttl) + } else if guard.contains(&(group, compact_task::TaskType::Tombstone)) { + Some(compact_task::TaskType::Tombstone) + } else { + None + } + } +} diff --git a/src/meta/src/hummock/manager/compaction_group_manager.rs b/src/meta/src/hummock/manager/compaction_group_manager.rs index 18bf9b263e734..c0c00d18a01e4 100644 --- a/src/meta/src/hummock/manager/compaction_group_manager.rs +++ b/src/meta/src/hummock/manager/compaction_group_manager.rs @@ -16,12 +16,10 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::ops::DerefMut; use std::sync::Arc; -use function_name::named; use itertools::Itertools; use risingwave_common::catalog::TableId; use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - build_version_delta_after_version, get_compaction_group_ids, get_compaction_group_ssts, - get_member_table_ids, try_get_compaction_group_id_by_table_id, TableGroupInfo, + get_compaction_group_ids, TableGroupInfo, }; use risingwave_hummock_sdk::compaction_group::{StateTableId, StaticCompactionGroupId}; use risingwave_hummock_sdk::CompactionGroupId; @@ -31,60 +29,82 @@ use risingwave_pb::hummock::group_delta::DeltaType; use risingwave_pb::hummock::hummock_version_delta::GroupDeltas; use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::mutable_config::MutableConfig; use risingwave_pb::hummock::subscribe_compaction_event_request::ReportTask; +use risingwave_pb::hummock::write_limits::WriteLimit; use risingwave_pb::hummock::{ compact_task, CompactionConfig, CompactionGroupInfo, CompatibilityVersion, GroupConstruct, - GroupDelta, GroupDestroy, GroupMetaChange, GroupTableChange, + GroupDelta, GroupDestroy, StateTableInfoDelta, }; -use thiserror_ext::AsReport; -use tokio::sync::{OnceCell, RwLock}; +use tokio::sync::OnceCell; -use super::write_lock; use crate::hummock::compaction::compaction_config::{ validate_compaction_config, CompactionConfigBuilder, }; use crate::hummock::error::{Error, Result}; -use crate::hummock::manager::{ - commit_multi_var, create_trx_wrapper, drop_sst, read_lock, HummockManager, -}; +use crate::hummock::manager::transaction::HummockVersionTransaction; +use crate::hummock::manager::versioning::Versioning; +use crate::hummock::manager::{commit_multi_var, HummockManager}; use crate::hummock::metrics_utils::remove_compaction_group_in_sst_stat; use crate::hummock::model::CompactionGroup; use crate::hummock::sequence::{next_compaction_group_id, next_sstable_object_id}; use crate::manager::{MetaSrvEnv, MetaStoreImpl}; -use crate::model::{ - BTreeMapEntryTransaction, BTreeMapEntryTransactionWrapper, BTreeMapTransaction, - BTreeMapTransactionWrapper, MetadataModel, MetadataModelError, ValTransaction, -}; -use crate::storage::MetaStore; +use crate::model::{BTreeMapTransaction, MetadataModel, MetadataModelError}; -impl HummockManager { - pub(super) async fn build_compaction_group_manager( - env: &MetaSrvEnv, - ) -> Result> { +type CompactionGroupTransaction<'a> = BTreeMapTransaction<'a, CompactionGroupId, CompactionGroup>; + +impl CompactionGroupManager { + pub(super) async fn new(env: &MetaSrvEnv) -> Result { let default_config = match env.opts.compaction_config.as_ref() { None => CompactionConfigBuilder::new().build(), Some(opt) => CompactionConfigBuilder::with_opt(opt).build(), }; - Self::build_compaction_group_manager_with_config(env, default_config).await + Self::new_with_config(env, default_config).await } - pub(super) async fn build_compaction_group_manager_with_config( + pub(super) async fn new_with_config( env: &MetaSrvEnv, default_config: CompactionConfig, - ) -> Result> { - let compaction_group_manager = RwLock::new(CompactionGroupManager { + ) -> Result { + let mut compaction_group_manager = CompactionGroupManager { compaction_groups: BTreeMap::new(), - default_config, - meta_store_impl: env.meta_store_ref(), - }); - compaction_group_manager.write().await.init().await?; + default_config: Arc::new(default_config), + write_limit: Default::default(), + }; + + let loaded_compaction_groups: BTreeMap = + match env.meta_store_ref() { + MetaStoreImpl::Kv(meta_store) => CompactionGroup::list(meta_store) + .await? + .into_iter() + .map(|cg| (cg.group_id(), cg)) + .collect(), + MetaStoreImpl::Sql(sql_meta_store) => { + use sea_orm::EntityTrait; + compaction_config::Entity::find() + .all(&sql_meta_store.conn) + .await + .map_err(MetadataModelError::from)? + .into_iter() + .map(|m| (m.compaction_group_id as CompactionGroupId, m.into())) + .collect() + } + }; + + compaction_group_manager.init(loaded_compaction_groups); Ok(compaction_group_manager) } + fn init(&mut self, loaded_compaction_groups: BTreeMap) { + if !loaded_compaction_groups.is_empty() { + self.compaction_groups = loaded_compaction_groups; + } + } +} + +impl HummockManager { /// Should not be called inside [`HummockManager`], because it requests locks internally. /// The implementation acquires `versioning` lock. - #[named] pub async fn compaction_group_ids(&self) -> Vec { - get_compaction_group_ids(&read_lock!(self, versioning).await.current_version).collect_vec() + get_compaction_group_ids(&self.versioning.read().await.current_version).collect_vec() } /// The implementation acquires `compaction_group_manager` lock. @@ -121,7 +141,7 @@ impl HummockManager { CompactionGroupId::from(StaticCompactionGroupId::StateDefault), )); } - self.register_table_ids(&pairs).await?; + self.register_table_ids_for_test(&pairs).await?; Ok(pairs.iter().map(|(table_id, ..)| *table_id).collect_vec()) } @@ -131,65 +151,75 @@ impl HummockManager { &self, table_fragments: &[crate::model::TableFragments], ) { - self.unregister_table_ids_fail_fast( - &table_fragments + self.unregister_table_ids( + table_fragments .iter() - .flat_map(|t| t.all_table_ids()) - .collect_vec(), + .flat_map(|t| t.all_table_ids().map(TableId::new)), ) - .await; + .await + .unwrap(); } /// Unregisters stale members and groups /// The caller should ensure `table_fragments_list` remain unchanged during `purge`. /// Currently `purge` is only called during meta service start ups. - #[named] - pub async fn purge(&self, valid_ids: &[u32]) -> Result<()> { - let registered_members = - get_member_table_ids(&read_lock!(self, versioning).await.current_version); - let to_unregister = registered_members - .into_iter() + pub async fn purge(&self, valid_ids: &HashSet) -> Result<()> { + let to_unregister = self + .versioning + .read() + .await + .current_version + .state_table_info + .info() + .keys() + .cloned() .filter(|table_id| !valid_ids.contains(table_id)) .collect_vec(); // As we have released versioning lock, the version that `to_unregister` is calculated from // may not be the same as the one used in unregister_table_ids. It is OK. - self.unregister_table_ids(&to_unregister).await + self.unregister_table_ids(to_unregister).await } /// The implementation acquires `versioning` lock. - #[named] - pub async fn register_table_ids( + /// + /// The method name is temporarily added with a `_for_test` prefix to mark + /// that it's currently only used in test. + pub async fn register_table_ids_for_test( &self, pairs: &[(StateTableId, CompactionGroupId)], ) -> Result<()> { if pairs.is_empty() { return Ok(()); } - let mut versioning_guard = write_lock!(self, versioning).await; + let mut versioning_guard = self.versioning.write().await; let versioning = versioning_guard.deref_mut(); + let mut compaction_group_manager = self.compaction_group_manager.write().await; let current_version = &versioning.current_version; + let default_config = compaction_group_manager.default_compaction_config(); + let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn(); for (table_id, _) in pairs { - if let Some(old_group) = - try_get_compaction_group_id_by_table_id(current_version, *table_id) + if let Some(info) = current_version + .state_table_info + .info() + .get(&TableId::new(*table_id)) { return Err(Error::CompactionGroup(format!( - "table {} already in group {}", - *table_id, old_group + "table {} already {:?}", + *table_id, info ))); } } // All NewCompactionGroup pairs are mapped to one new compaction group. let new_compaction_group_id: OnceCell = OnceCell::new(); - let mut new_version_delta = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapEntryTransactionWrapper, - BTreeMapEntryTransaction::new_insert( - &mut versioning.hummock_version_deltas, - current_version.id + 1, - build_version_delta_after_version(current_version), - ) + let mut version = HummockVersionTransaction::new( + &mut versioning.current_version, + &mut versioning.hummock_version_deltas, + self.env.notification_manager(), + &self.metrics, ); + let mut new_version_delta = version.new_delta(); + let epoch = new_version_delta.latest_version().max_committed_epoch; for (table_id, raw_group_id) in pairs { let mut group_id = *raw_group_id; @@ -211,15 +241,17 @@ impl HummockManager { .entry(group_id) .or_default() .group_deltas; - let config = self - .compaction_group_manager - .write() - .await - .get_or_insert_compaction_group_config(group_id) - .await? - .compaction_config - .as_ref() - .clone(); + + let config = + match compaction_groups_txn.try_get_compaction_group_config(group_id) { + Some(config) => config.compaction_config.as_ref().clone(), + None => { + compaction_groups_txn + .create_compaction_groups(group_id, default_config.clone()); + default_config.as_ref().clone() + } + }; + group_deltas.push(GroupDelta { delta_type: Some(DeltaType::GroupConstruct(GroupConstruct { group_config: Some(config), @@ -229,104 +261,83 @@ impl HummockManager { }); } } - let group_deltas = &mut new_version_delta - .group_deltas - .entry(group_id) - .or_default() - .group_deltas; - group_deltas.push(GroupDelta { - delta_type: Some(DeltaType::GroupMetaChange(GroupMetaChange { - table_ids_add: vec![*table_id], - ..Default::default() - })), - }); + assert!(new_version_delta + .state_table_info_delta + .insert( + TableId::new(*table_id), + StateTableInfoDelta { + committed_epoch: epoch, + safe_epoch: epoch, + compaction_group_id: *raw_group_id, + } + ) + .is_none()); } - let mut current_version = versioning.current_version.clone(); - let sst_split_info = current_version.apply_version_delta(&new_version_delta); - assert!(sst_split_info.is_empty()); - commit_multi_var!(self.meta_store_ref(), new_version_delta)?; - versioning.current_version = current_version; - - self.notify_last_version_delta(versioning); + new_version_delta.pre_apply(); + commit_multi_var!(self.meta_store_ref(), version, compaction_groups_txn)?; Ok(()) } - #[named] - pub async fn unregister_table_ids(&self, table_ids: &[StateTableId]) -> Result<()> { - if table_ids.is_empty() { + pub async fn unregister_table_ids( + &self, + table_ids: impl IntoIterator + Send, + ) -> Result<()> { + let mut table_ids = table_ids.into_iter().peekable(); + if table_ids.peek().is_none() { return Ok(()); } - let mut versioning_guard = write_lock!(self, versioning).await; + let mut versioning_guard = self.versioning.write().await; let versioning = versioning_guard.deref_mut(); - let current_version = &versioning.current_version; - let mut new_version_delta = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapEntryTransactionWrapper, - BTreeMapEntryTransaction::new_insert( - &mut versioning.hummock_version_deltas, - current_version.id + 1, - build_version_delta_after_version(current_version), - ) + let mut version = HummockVersionTransaction::new( + &mut versioning.current_version, + &mut versioning.hummock_version_deltas, + self.env.notification_manager(), + &self.metrics, ); + let mut new_version_delta = version.new_delta(); let mut modified_groups: HashMap = HashMap::new(); // Remove member tables - for table_id in table_ids.iter().unique() { - let group_id = match try_get_compaction_group_id_by_table_id(current_version, *table_id) - { - Some(group_id) => group_id, - None => continue, + for table_id in table_ids.unique() { + let version = new_version_delta.latest_version(); + let Some(info) = version.state_table_info.info().get(&table_id) else { + continue; }; - let group_deltas = &mut new_version_delta - .group_deltas - .entry(group_id) - .or_default() - .group_deltas; - group_deltas.push(GroupDelta { - delta_type: Some(DeltaType::GroupMetaChange(GroupMetaChange { - table_ids_remove: vec![*table_id], - ..Default::default() - })), - }); + modified_groups - .entry(group_id) + .entry(info.compaction_group_id) .and_modify(|count| *count -= 1) .or_insert( - current_version - .get_compaction_group_levels(group_id) - .member_table_ids + version + .state_table_info + .compaction_group_member_tables() + .get(&info.compaction_group_id) + .expect("should exist") .len() as u64 - 1, ); - new_version_delta - .removed_table_ids - .push(TableId::new(*table_id)); + new_version_delta.removed_table_ids.insert(table_id); } - // Remove empty group, GC SSTs and remove metric. - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); let groups_to_remove = modified_groups .into_iter() .filter_map(|(group_id, member_count)| { if member_count == 0 && group_id > StaticCompactionGroupId::End as CompactionGroupId { - return Some(group_id); + return Some(( + group_id, + new_version_delta + .latest_version() + .get_compaction_group_levels(group_id) + .get_levels() + .len(), + )); } None }) .collect_vec(); - for group_id in &groups_to_remove { - // We don't bother to add IntraLevelDelta to remove SSTs from group, because the entire - // group is to be removed. - // However, we need to take care of SST GC for the removed group. - for (object_id, sst_id) in get_compaction_group_ssts(current_version, *group_id) { - drop_sst(&mut branched_ssts, *group_id, object_id, sst_id); - } + for (group_id, _) in &groups_to_remove { let group_deltas = &mut new_version_delta .group_deltas .entry(*group_id) @@ -336,91 +347,81 @@ impl HummockManager { delta_type: Some(DeltaType::GroupDestroy(GroupDestroy {})), }); } - let mut current_version = versioning.current_version.clone(); - let sst_split_info = current_version.apply_version_delta(&new_version_delta); - assert!(sst_split_info.is_empty()); - commit_multi_var!(self.meta_store_ref(), new_version_delta)?; - for group_id in &groups_to_remove { - let max_level = versioning - .current_version - .get_compaction_group_levels(*group_id) - .get_levels() - .len(); - remove_compaction_group_in_sst_stat(&self.metrics, *group_id, max_level); + for (group_id, max_level) in groups_to_remove { + remove_compaction_group_in_sst_stat(&self.metrics, group_id, max_level); } - versioning.current_version = current_version; - branched_ssts.commit_memory(); - self.notify_last_version_delta(versioning); + new_version_delta.pre_apply(); // Purge may cause write to meta store. If it hurts performance while holding versioning // lock, consider to make it in batch. - self.compaction_group_manager - .write() - .await - .purge(HashSet::from_iter(get_compaction_group_ids( - &versioning.current_version, - ))) - .await?; - Ok(()) - } + let mut compaction_group_manager = self.compaction_group_manager.write().await; + let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn(); - /// The implementation acquires `versioning` lock and `compaction_group_manager` lock. - pub async fn unregister_table_ids_fail_fast(&self, table_ids: &[StateTableId]) { - self.unregister_table_ids(table_ids) - .await - .unwrap_or_else(|e| { - panic!("unregister table ids fail: {table_ids:?} {}", e.as_report()) - }); + compaction_groups_txn.purge(HashSet::from_iter(get_compaction_group_ids( + version.latest_version(), + ))); + commit_multi_var!(self.meta_store_ref(), version, compaction_groups_txn)?; + + Ok(()) } pub async fn update_compaction_config( &self, compaction_group_ids: &[CompactionGroupId], config_to_update: &[MutableConfig], - ) -> Result> { - let result = self - .compaction_group_manager - .write() - .await - .update_compaction_config(compaction_group_ids, config_to_update) - .await?; + ) -> Result<()> { + { + // Avoid lock conflicts with `try_update_write_limits`` + let mut compaction_group_manager = self.compaction_group_manager.write().await; + let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn(); + compaction_groups_txn + .update_compaction_config(compaction_group_ids, config_to_update)?; + commit_multi_var!(self.meta_store_ref(), compaction_groups_txn)?; + } + if config_to_update .iter() .any(|c| matches!(c, MutableConfig::Level0StopWriteThresholdSubLevelNumber(_))) { + // Update write limits with lock self.try_update_write_limits(compaction_group_ids).await; } - Ok(result) + Ok(()) } /// Gets complete compaction group info. /// It is the aggregate of `HummockVersion` and `CompactionGroupConfig` - #[named] pub async fn list_compaction_group(&self) -> Vec { - let mut versioning_guard = write_lock!(self, versioning).await; + let mut versioning_guard = self.versioning.write().await; let versioning = versioning_guard.deref_mut(); let current_version = &versioning.current_version; - let mut compaction_groups = vec![]; + let mut results = vec![]; + let compaction_group_manager = self.compaction_group_manager.read().await; + for levels in current_version.levels.values() { - let config = self - .compaction_group_manager - .read() - .await + let compaction_config = compaction_group_manager .try_get_compaction_group_config(levels.group_id) .unwrap() - .compaction_config; + .compaction_config + .as_ref() + .clone(); let group = CompactionGroupInfo { id: levels.group_id, parent_id: levels.parent_group_id, - member_table_ids: levels.member_table_ids.clone(), - compaction_config: Some(config.as_ref().clone()), + member_table_ids: current_version + .state_table_info + .compaction_group_member_table_ids(levels.group_id) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), + compaction_config: Some(compaction_config), }; - compaction_groups.push(group); + results.push(group); } - compaction_groups + results } /// Splits a compaction group into two. The new one will contain `table_ids`. @@ -434,44 +435,47 @@ impl HummockManager { .move_state_table_to_compaction_group( parent_group_id, table_ids, - None, self.env.opts.partition_vnode_count, ) .await?; - self.group_to_table_vnode_partition - .write() - .insert(result.0, result.1); - Ok(result.0) + Ok(result) } /// move some table to another compaction-group. Create a new compaction group if it does not /// exist. - /// TODO: Move table_to_partition in result to compaction group - #[named] pub async fn move_state_table_to_compaction_group( &self, parent_group_id: CompactionGroupId, table_ids: &[StateTableId], - target_group_id: Option, partition_vnode_count: u32, - ) -> Result<(CompactionGroupId, BTreeMap)> { - let mut table_to_partition = BTreeMap::default(); + ) -> Result { if table_ids.is_empty() { - return Ok((parent_group_id, table_to_partition)); + return Ok(parent_group_id); } let table_ids = table_ids.iter().cloned().unique().collect_vec(); - let compaction_guard = write_lock!(self, compaction).await; - let mut versioning_guard = write_lock!(self, versioning).await; + let compaction_guard = self.compaction.write().await; + let mut versioning_guard = self.versioning.write().await; let versioning = versioning_guard.deref_mut(); - let current_version = &versioning.current_version; // Validate parameters. - let parent_group = current_version + if !versioning + .current_version .levels - .get(&parent_group_id) - .ok_or_else(|| Error::CompactionGroup(format!("invalid group {}", parent_group_id)))?; + .contains_key(&parent_group_id) + { + return Err(Error::CompactionGroup(format!( + "invalid group {}", + parent_group_id + ))); + } + for table_id in &table_ids { - if !parent_group.member_table_ids.contains(table_id) { + if !versioning + .current_version + .state_table_info + .compaction_group_member_table_ids(parent_group_id) + .contains(&TableId::new(*table_id)) + { return Err(Error::CompactionGroup(format!( "table {} doesn't in group {}", table_id, parent_group_id @@ -479,79 +483,38 @@ impl HummockManager { } } - if table_ids.len() == parent_group.member_table_ids.len() { + if table_ids.len() + == versioning + .current_version + .state_table_info + .compaction_group_member_table_ids(parent_group_id) + .len() + { return Err(Error::CompactionGroup(format!( "invalid split attempt for group {}: all member tables are moved", parent_group_id ))); } - if let Some(compaction_group_id) = target_group_id { - if !versioning.check_branched_sst_in_target_group( - &table_ids, - &parent_group_id, - &compaction_group_id, - ) { - return Err(Error::CompactionGroup(format!( - "invalid split attempt for group {}: we shall wait some time for parent group and target group could compact stale sst files", - parent_group_id - ))); - } - } - let mut new_version_delta = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapEntryTransactionWrapper, - BTreeMapEntryTransaction::new_insert( - &mut versioning.hummock_version_deltas, - current_version.id + 1, - build_version_delta_after_version(current_version), - ) + let mut version = HummockVersionTransaction::new( + &mut versioning.current_version, + &mut versioning.hummock_version_deltas, + self.env.notification_manager(), + &self.metrics, ); + let mut new_version_delta = version.new_delta(); + let new_sst_start_id = next_sstable_object_id( &self.env, - current_version.count_new_ssts_in_group_split( - parent_group_id, - HashSet::from_iter(table_ids.clone()), - ), + new_version_delta + .latest_version() + .count_new_ssts_in_group_split( + parent_group_id, + HashSet::from_iter(table_ids.clone()), + ), ) .await?; - let mut new_group = None; - let target_compaction_group_id = match target_group_id { - Some(compaction_group_id) => { - match current_version.levels.get(&compaction_group_id) { - Some(group) => { - for table_id in &table_ids { - if group.member_table_ids.contains(table_id) { - return Err(Error::CompactionGroup(format!( - "table {} already exist in group {}", - *table_id, compaction_group_id, - ))); - } - } - } - None => { - return Err(Error::CompactionGroup(format!( - "target group {} does not exist", - compaction_group_id, - ))); - } - } - let group_deltas = &mut new_version_delta - .group_deltas - .entry(compaction_group_id) - .or_default() - .group_deltas; - group_deltas.push(GroupDelta { - delta_type: Some(DeltaType::GroupTableChange(GroupTableChange { - table_ids: table_ids.to_vec(), - origin_group_id: parent_group_id, - target_group_id: compaction_group_id, - new_sst_start_id, - version: CompatibilityVersion::NoTrivialSplit as i32, - })), - }); - compaction_group_id - } - None => { + let (new_group, target_compaction_group_id) = { + { // All NewCompactionGroup pairs are mapped to one new compaction group. let new_compaction_group_id = next_compaction_group_id(&self.env).await?; // The new config will be persisted later. @@ -559,9 +522,13 @@ impl HummockManager { .compaction_group_manager .read() .await - .default_compaction_config(); + .default_compaction_config() + .as_ref() + .clone(); config.split_weight_by_vnode = partition_vnode_count; + #[expect(deprecated)] + // fill the deprecated field with default value new_version_delta.group_deltas.insert( new_compaction_group_id, GroupDeltas { @@ -571,92 +538,53 @@ impl HummockManager { group_id: new_compaction_group_id, parent_group_id, new_sst_start_id, - table_ids: table_ids.to_vec(), - version: CompatibilityVersion::NoTrivialSplit as i32, - })), - }], - }, - ); - - new_group = Some((new_compaction_group_id, config)); - new_version_delta.group_deltas.insert( - parent_group_id, - GroupDeltas { - group_deltas: vec![GroupDelta { - delta_type: Some(DeltaType::GroupMetaChange(GroupMetaChange { - table_ids_remove: table_ids.to_vec(), - ..Default::default() + table_ids: vec![], + version: CompatibilityVersion::NoMemberTableIds as i32, })), }], }, ); - new_compaction_group_id + ((new_compaction_group_id, config), new_compaction_group_id) } }; - let mut current_version = versioning.current_version.clone(); - let sst_split_info = current_version.apply_version_delta(&new_version_delta); - - // `branched_ssts` only commit in memory, so `TXN` make no difference. - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); - if let Some((new_compaction_group_id, config)) = new_group { - let mut compaction_group_manager = self.compaction_group_manager.write().await; - let insert = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapEntryTransactionWrapper, - BTreeMapEntryTransaction::new_insert( - &mut compaction_group_manager.compaction_groups, - new_compaction_group_id, - CompactionGroup { - group_id: new_compaction_group_id, - compaction_config: Arc::new(config), - }, - ) - ); - commit_multi_var!(self.meta_store_ref(), new_version_delta, insert)?; - // Currently, only splitting out a single table_id is supported. - for table_id in table_ids { - table_to_partition.insert(table_id, partition_vnode_count); - } - } else { - commit_multi_var!(self.meta_store_ref(), new_version_delta)?; - } - versioning.current_version = current_version; - // Updates SST split info - let mut changed_sst_ids: HashSet = HashSet::default(); - for (object_id, sst_id, parent_old_sst_id, parent_new_sst_id) in sst_split_info { - changed_sst_ids.insert(parent_old_sst_id); - match branched_ssts.get_mut(object_id) { - Some(mut entry) => { - entry.insert(parent_group_id, parent_new_sst_id); - entry.insert(target_compaction_group_id, sst_id); - } - None => { - let mut groups = HashMap::from_iter([(target_compaction_group_id, sst_id)]); - groups.insert(parent_group_id, parent_new_sst_id); - branched_ssts.insert(object_id, groups); - } + let (new_compaction_group_id, config) = new_group; + new_version_delta.with_latest_version(|version, new_version_delta| { + for table_id in &table_ids { + let table_id = TableId::new(*table_id); + let info = version + .state_table_info + .info() + .get(&table_id) + .expect("have check exist previously"); + assert!(new_version_delta + .state_table_info_delta + .insert( + table_id, + StateTableInfoDelta { + committed_epoch: info.committed_epoch, + safe_epoch: info.safe_epoch, + compaction_group_id: new_compaction_group_id, + } + ) + .is_none()); } + }); + { + let mut compaction_group_manager = self.compaction_group_manager.write().await; + let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn(); + compaction_groups_txn + .create_compaction_groups(new_compaction_group_id, Arc::new(config)); + + new_version_delta.pre_apply(); + commit_multi_var!(self.meta_store_ref(), version, compaction_groups_txn)?; } - branched_ssts.commit_memory(); - self.notify_last_version_delta(versioning); - drop(versioning_guard); + let mut canceled_tasks = vec![]; for task_assignment in compaction_guard.compact_task_assignment.values() { - let mut need_cancel = false; if let Some(task) = task_assignment.compact_task.as_ref() { - for input_level in &task.input_ssts { - for sst in &input_level.table_infos { - if changed_sst_ids.contains(&sst.sst_id) { - need_cancel = true; - break; - } - } - } + let need_cancel = + HummockManager::is_compact_task_expired(task, &versioning.current_version); if need_cancel { canceled_tasks.push(ReportTask { task_id: task.task_id, @@ -668,6 +596,7 @@ impl HummockManager { } } + drop(versioning_guard); drop(compaction_guard); self.report_compact_tasks(canceled_tasks).await?; @@ -686,30 +615,34 @@ impl HummockManager { .with_label_values(&[&parent_group_id.to_string()]) .inc(); - Ok((target_compaction_group_id, table_to_partition)) + Ok(target_compaction_group_id) } - #[named] pub async fn calculate_compaction_group_statistic(&self) -> Vec { let mut infos = vec![]; { - let versioning_guard = read_lock!(self, versioning).await; + let versioning_guard = self.versioning.read().await; let version = &versioning_guard.current_version; - for (group_id, group) in &version.levels { + for group_id in version.levels.keys() { let mut group_info = TableGroupInfo { group_id: *group_id, ..Default::default() }; - for table_id in &group.member_table_ids { + for table_id in version + .state_table_info + .compaction_group_member_table_ids(*group_id) + { let stats_size = versioning_guard .version_stats .table_stats - .get(table_id) + .get(&table_id.table_id) .map(|stats| stats.total_key_size + stats.total_value_size) .unwrap_or(0); let table_size = std::cmp::max(stats_size, 0) as u64; group_info.group_size += table_size; - group_info.table_statistic.insert(*table_id, table_size); + group_info + .table_statistic + .insert(table_id.table_id, table_size); } infos.push(group_info); } @@ -722,6 +655,22 @@ impl HummockManager { } infos } + + pub(super) async fn initial_compaction_group_config_after_load( + &self, + versioning_guard: &Versioning, + compaction_group_manager: &mut CompactionGroupManager, + ) -> Result<()> { + // 1. Due to version compatibility, we fix some of the configuration of older versions after hummock starts. + let current_version = &versioning_guard.current_version; + let all_group_ids = get_compaction_group_ids(current_version).collect_vec(); + let default_config = compaction_group_manager.default_compaction_config(); + let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn(); + compaction_groups_txn.try_create_compaction_groups(&all_group_ids, default_config); + commit_multi_var!(self.meta_store_ref(), compaction_groups_txn)?; + + Ok(()) + } } /// We muse ensure there is an entry exists in [`CompactionGroupManager`] for any @@ -733,71 +682,15 @@ impl HummockManager { /// 3. move existent table to new compaction group. pub(super) struct CompactionGroupManager { compaction_groups: BTreeMap, - default_config: CompactionConfig, - meta_store_impl: MetaStoreImpl, + default_config: Arc, + /// Tables that write limit is trigger for. + pub write_limit: HashMap, } impl CompactionGroupManager { - async fn init(&mut self) -> Result<()> { - let loaded_compaction_groups: BTreeMap = - match &self.meta_store_impl { - MetaStoreImpl::Kv(meta_store) => CompactionGroup::list(meta_store) - .await? - .into_iter() - .map(|cg| (cg.group_id(), cg)) - .collect(), - MetaStoreImpl::Sql(sql_meta_store) => { - use sea_orm::EntityTrait; - compaction_config::Entity::find() - .all(&sql_meta_store.conn) - .await - .map_err(MetadataModelError::from)? - .into_iter() - .map(|m| (m.compaction_group_id as CompactionGroupId, m.into())) - .collect() - } - }; - if !loaded_compaction_groups.is_empty() { - self.compaction_groups = loaded_compaction_groups; - } - Ok(()) - } - - /// Gets compaction group config for `compaction_group_id`, inserts default one if missing. - pub(super) async fn get_or_insert_compaction_group_config( - &mut self, - compaction_group_id: CompactionGroupId, - ) -> Result { - let r = self - .get_or_insert_compaction_group_configs(&[compaction_group_id]) - .await?; - Ok(r.into_values().next().unwrap()) - } - - /// Gets compaction group configs for `compaction_group_ids`, inserts default one if missing. - pub(super) async fn get_or_insert_compaction_group_configs( - &mut self, - compaction_group_ids: &[CompactionGroupId], - ) -> Result> { - let mut compaction_groups = create_trx_wrapper!( - self.meta_store_impl, - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut self.compaction_groups,) - ); - for id in compaction_group_ids { - if compaction_groups.contains_key(id) { - continue; - } - let new_entry = CompactionGroup::new(*id, self.default_config.clone()); - compaction_groups.insert(*id, new_entry); - } - commit_multi_var!(self.meta_store_impl, compaction_groups)?; - - let r = compaction_group_ids - .iter() - .map(|id| (*id, self.compaction_groups[id].clone())) - .collect(); - Ok(r) + /// Starts a transaction to update compaction group configs. + pub fn start_compaction_groups_txn(&mut self) -> CompactionGroupTransaction<'_> { + CompactionGroupTransaction::new(&mut self.compaction_groups) } /// Tries to get compaction group config for `compaction_group_id`. @@ -808,84 +701,10 @@ impl CompactionGroupManager { self.compaction_groups.get(&compaction_group_id).cloned() } - pub(super) fn default_compaction_config(&self) -> CompactionConfig { + /// Tries to get compaction group config for `compaction_group_id`. + pub(super) fn default_compaction_config(&self) -> Arc { self.default_config.clone() } - - pub async fn update_compaction_config( - &mut self, - compaction_group_ids: &[CompactionGroupId], - config_to_update: &[MutableConfig], - ) -> Result> { - let mut compaction_groups = create_trx_wrapper!( - self.meta_store_impl, - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut self.compaction_groups,) - ); - let mut result = Vec::with_capacity(compaction_group_ids.len()); - for compaction_group_id in compaction_group_ids.iter().unique() { - let group = compaction_groups.get(compaction_group_id).ok_or_else(|| { - Error::CompactionGroup(format!("invalid group {}", *compaction_group_id)) - })?; - let mut config = group.compaction_config.as_ref().clone(); - update_compaction_config(&mut config, config_to_update); - if let Err(reason) = validate_compaction_config(&config) { - return Err(Error::CompactionGroup(reason)); - } - let mut new_group = group.clone(); - new_group.compaction_config = Arc::new(config); - compaction_groups.insert(*compaction_group_id, new_group.clone()); - result.push(new_group); - } - commit_multi_var!(self.meta_store_impl, compaction_groups)?; - Ok(result) - } - - /// Initializes the config for a group. - /// Should only be used by compaction test. - pub async fn init_compaction_config_for_replay( - &mut self, - group_id: CompactionGroupId, - config: CompactionConfig, - ) -> Result<()> { - let insert = create_trx_wrapper!( - self.meta_store_impl, - BTreeMapEntryTransactionWrapper, - BTreeMapEntryTransaction::new_insert( - &mut self.compaction_groups, - group_id, - CompactionGroup { - group_id, - compaction_config: Arc::new(config), - }, - ) - ); - commit_multi_var!(self.meta_store_impl, insert)?; - Ok(()) - } - - /// Removes stale group configs. - async fn purge(&mut self, existing_groups: HashSet) -> Result<()> { - let mut compaction_groups = create_trx_wrapper!( - self.meta_store_impl, - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut self.compaction_groups,) - ); - let stale_group = compaction_groups - .tree_ref() - .keys() - .cloned() - .filter(|k| !existing_groups.contains(k)) - .collect_vec(); - if stale_group.is_empty() { - return Ok(()); - } - for group in stale_group { - compaction_groups.remove(group); - } - commit_multi_var!(self.meta_store_impl, compaction_groups)?; - Ok(()) - } } fn update_compaction_config(target: &mut CompactionConfig, items: &[MutableConfig]) { @@ -936,58 +755,158 @@ fn update_compaction_config(target: &mut CompactionConfig, items: &[MutableConfi MutableConfig::TombstoneReclaimRatio(c) => { target.tombstone_reclaim_ratio = *c; } + MutableConfig::CompressionAlgorithm(c) => { + target.compression_algorithm[c.get_level() as usize] + .clone_from(&c.compression_algorithm); + } + MutableConfig::MaxL0CompactLevelCount(c) => { + target.max_l0_compact_level_count = *c; + } } } } +impl<'a> CompactionGroupTransaction<'a> { + /// Inserts compaction group configs if they do not exist. + pub fn try_create_compaction_groups( + &mut self, + compaction_group_ids: &[CompactionGroupId], + config: Arc, + ) -> bool { + let mut trivial = true; + for id in compaction_group_ids { + if self.contains_key(id) { + continue; + } + let new_entry = CompactionGroup::new(*id, config.as_ref().clone()); + self.insert(*id, new_entry); + + trivial = false; + } + + !trivial + } + + pub fn create_compaction_groups( + &mut self, + compaction_group_id: CompactionGroupId, + config: Arc, + ) { + self.try_create_compaction_groups(&[compaction_group_id], config); + } + + /// Tries to get compaction group config for `compaction_group_id`. + pub(super) fn try_get_compaction_group_config( + &self, + compaction_group_id: CompactionGroupId, + ) -> Option<&CompactionGroup> { + self.get(&compaction_group_id) + } + + /// Removes stale group configs. + fn purge(&mut self, existing_groups: HashSet) { + let stale_group = self + .tree_ref() + .keys() + .cloned() + .filter(|k| !existing_groups.contains(k)) + .collect_vec(); + if stale_group.is_empty() { + return; + } + for group in stale_group { + self.remove(group); + } + } + + pub(super) fn update_compaction_config( + &mut self, + compaction_group_ids: &[CompactionGroupId], + config_to_update: &[MutableConfig], + ) -> Result> { + let mut results = HashMap::default(); + for compaction_group_id in compaction_group_ids.iter().unique() { + let group = self.get(compaction_group_id).ok_or_else(|| { + Error::CompactionGroup(format!("invalid group {}", *compaction_group_id)) + })?; + let mut config = group.compaction_config.as_ref().clone(); + update_compaction_config(&mut config, config_to_update); + if let Err(reason) = validate_compaction_config(&config) { + return Err(Error::CompactionGroup(reason)); + } + let mut new_group = group.clone(); + new_group.compaction_config = Arc::new(config); + self.insert(*compaction_group_id, new_group.clone()); + results.insert(new_group.group_id(), new_group); + } + + Ok(results) + } +} + #[cfg(test)] mod tests { - use std::collections::BTreeMap; + use std::collections::{BTreeMap, HashSet}; - use itertools::Itertools; use risingwave_common::catalog::TableId; use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::mutable_config::MutableConfig; use risingwave_pb::meta::table_fragments::Fragment; + use crate::hummock::commit_multi_var; + use crate::hummock::error::Result; + use crate::hummock::manager::compaction_group_manager::CompactionGroupManager; use crate::hummock::test_utils::setup_compute_env; - use crate::hummock::HummockManager; + use crate::manager::MetaStoreImpl; use crate::model::TableFragments; #[tokio::test] async fn test_inner() { let (env, ..) = setup_compute_env(8080).await; - let inner = HummockManager::build_compaction_group_manager(&env) - .await - .unwrap(); - assert_eq!(inner.read().await.compaction_groups.len(), 2); - inner - .write() - .await - .update_compaction_config(&[100, 200], &[]) + let mut inner = CompactionGroupManager::new(&env).await.unwrap(); + assert_eq!(inner.compaction_groups.len(), 2); + + async fn update_compaction_config( + meta: &MetaStoreImpl, + inner: &mut CompactionGroupManager, + cg_ids: &[u64], + config_to_update: &[MutableConfig], + ) -> Result<()> { + let mut compaction_groups_txn = inner.start_compaction_groups_txn(); + compaction_groups_txn.update_compaction_config(cg_ids, config_to_update)?; + commit_multi_var!(meta, compaction_groups_txn) + } + + async fn insert_compaction_group_configs( + meta: &MetaStoreImpl, + inner: &mut CompactionGroupManager, + cg_ids: &[u64], + ) { + let default_config = inner.default_compaction_config(); + let mut compaction_groups_txn = inner.start_compaction_groups_txn(); + if compaction_groups_txn.try_create_compaction_groups(cg_ids, default_config) { + commit_multi_var!(meta, compaction_groups_txn).unwrap(); + } + } + + update_compaction_config(env.meta_store_ref(), &mut inner, &[100, 200], &[]) .await .unwrap_err(); - inner - .write() - .await - .get_or_insert_compaction_group_configs(&[100, 200]) - .await - .unwrap(); - assert_eq!(inner.read().await.compaction_groups.len(), 4); - let inner = HummockManager::build_compaction_group_manager(&env) - .await - .unwrap(); - assert_eq!(inner.read().await.compaction_groups.len(), 4); - inner - .write() - .await - .update_compaction_config(&[100, 200], &[MutableConfig::MaxSubCompaction(123)]) - .await - .unwrap(); - assert_eq!(inner.read().await.compaction_groups.len(), 4); + insert_compaction_group_configs(env.meta_store_ref(), &mut inner, &[100, 200]).await; + assert_eq!(inner.compaction_groups.len(), 4); + let mut inner = CompactionGroupManager::new(&env).await.unwrap(); + assert_eq!(inner.compaction_groups.len(), 4); + + update_compaction_config( + env.meta_store_ref(), + &mut inner, + &[100, 200], + &[MutableConfig::MaxSubCompaction(123)], + ) + .await + .unwrap(); + assert_eq!(inner.compaction_groups.len(), 4); assert_eq!( inner - .read() - .await .try_get_compaction_group_config(100) .unwrap() .compaction_config @@ -996,8 +915,6 @@ mod tests { ); assert_eq!( inner - .read() - .await .try_get_compaction_group_config(200) .unwrap() .compaction_config @@ -1070,11 +987,14 @@ mod tests { // Test purge_stale_members: table fragments compaction_group_manager - .purge(&table_fragment_2.all_table_ids().collect_vec()) + .purge(&table_fragment_2.all_table_ids().map(TableId::new).collect()) .await .unwrap(); assert_eq!(registered_number().await, 4); - compaction_group_manager.purge(&[]).await.unwrap(); + compaction_group_manager + .purge(&HashSet::new()) + .await + .unwrap(); assert_eq!(registered_number().await, 0); assert_eq!(group_number().await, 2); diff --git a/src/meta/src/hummock/manager/context.rs b/src/meta/src/hummock/manager/context.rs index 21aeb8402838b..982a94fd5f9db 100644 --- a/src/meta/src/hummock/manager/context.rs +++ b/src/meta/src/hummock/manager/context.rs @@ -12,35 +12,66 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{HashMap, HashSet}; -use std::ops::DerefMut; +use std::collections::{BTreeMap, HashMap, HashSet}; use fail::fail_point; -use function_name::named; use itertools::Itertools; +use risingwave_common::util::epoch::INVALID_EPOCH; use risingwave_hummock_sdk::version::HummockVersion; use risingwave_hummock_sdk::{ - ExtendedSstableInfo, HummockContextId, HummockEpoch, HummockSstableObjectId, + ExtendedSstableInfo, HummockContextId, HummockEpoch, HummockSstableObjectId, HummockVersionId, + INVALID_VERSION_ID, +}; +use risingwave_pb::hummock::{ + HummockPinnedSnapshot, HummockPinnedVersion, HummockSnapshot, ValidationTask, }; -use risingwave_pb::hummock::ValidationTask; use crate::hummock::error::{Error, Result}; -use crate::hummock::manager::{ - commit_multi_var, create_trx_wrapper, read_lock, start_measure_real_process_timer, write_lock, +use crate::hummock::manager::worker::{HummockManagerEvent, HummockManagerEventSender}; +use crate::hummock::manager::{commit_multi_var, start_measure_real_process_timer}; +use crate::hummock::metrics_utils::{ + trigger_pin_unpin_snapshot_state, trigger_pin_unpin_version_state, }; use crate::hummock::HummockManager; -use crate::manager::META_NODE_ID; -use crate::model::{BTreeMapTransaction, BTreeMapTransactionWrapper, ValTransaction}; -use crate::storage::MetaStore; +use crate::manager::{MetaStoreImpl, MetadataManager, META_NODE_ID}; +use crate::model::BTreeMapTransaction; +use crate::rpc::metrics::MetaMetrics; -impl HummockManager { +/// `HummockVersionSafePoint` prevents hummock versions GE than it from being GC. +/// It's used by meta node itself to temporarily pin versions. +pub struct HummockVersionSafePoint { + pub id: HummockVersionId, + event_sender: HummockManagerEventSender, +} + +impl Drop for HummockVersionSafePoint { + fn drop(&mut self) { + if self + .event_sender + .send(HummockManagerEvent::DropSafePoint(self.id)) + .is_err() + { + tracing::debug!("failed to drop hummock version safe point {}", self.id); + } + } +} + +#[derive(Default)] +pub(super) struct ContextInfo { + pub pinned_versions: BTreeMap, + pub pinned_snapshots: BTreeMap, + /// `version_safe_points` is similar to `pinned_versions` expect for being a transient state. + pub version_safe_points: Vec, +} + +impl ContextInfo { /// Release resources pinned by these contexts, including: /// - Version /// - Snapshot - #[named] - pub async fn release_contexts( - &self, + async fn release_contexts( + &mut self, context_ids: impl AsRef<[HummockContextId]>, + meta_store_ref: MetaStoreImpl, ) -> Result<()> { fail_point!("release_contexts_metastore_err", |_| Err(Error::MetaStore( anyhow::anyhow!("failpoint metastore error") @@ -49,50 +80,91 @@ impl HummockManager { anyhow::anyhow!("failpoint internal error") ))); - let mut versioning_guard = write_lock!(self, versioning).await; - let versioning = versioning_guard.deref_mut(); - let mut pinned_versions = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.pinned_versions,) - ); - let mut pinned_snapshots = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.pinned_snapshots,) - ); + let mut pinned_versions = BTreeMapTransaction::new(&mut self.pinned_versions); + let mut pinned_snapshots = BTreeMapTransaction::new(&mut self.pinned_snapshots); for context_id in context_ids.as_ref() { pinned_versions.remove(*context_id); pinned_snapshots.remove(*context_id); } - commit_multi_var!(self.meta_store_ref(), pinned_versions, pinned_snapshots)?; + commit_multi_var!(meta_store_ref, pinned_versions, pinned_snapshots)?; + Ok(()) + } +} + +impl HummockManager { + pub async fn release_contexts( + &self, + context_ids: impl AsRef<[HummockContextId]>, + ) -> Result<()> { + let mut context_info = self.context_info.write().await; + context_info + .release_contexts(context_ids, self.env.meta_store()) + .await?; #[cfg(test)] { - drop(versioning_guard); + drop(context_info); self.check_state_consistency().await; } - Ok(()) } /// Checks whether `context_id` is valid. pub async fn check_context(&self, context_id: HummockContextId) -> Result { - Ok(self - .metadata_manager() + self.context_info + .read() + .await + .check_context(context_id, &self.metadata_manager) + .await + } + + async fn check_context_with_meta_node( + &self, + context_id: HummockContextId, + context_info: &ContextInfo, + ) -> Result<()> { + if context_id == META_NODE_ID { + // Using the preserved meta id is allowed. + } else if !context_info + .check_context(context_id, &self.metadata_manager) + .await? + { + // The worker is not found in cluster. + return Err(Error::InvalidContext(context_id)); + } + Ok(()) + } + + #[cfg(any(test, feature = "test"))] + pub async fn get_min_pinned_version_id(&self) -> HummockVersionId { + self.context_info.read().await.min_pinned_version_id() + } +} + +impl ContextInfo { + /// Checks whether `context_id` is valid. + /// + /// Need `&self` to sync with `release_context` + pub(super) async fn check_context( + &self, + context_id: HummockContextId, + metadata_manager: &MetadataManager, + ) -> Result { + Ok(metadata_manager .get_worker_by_id(context_id) .await .map_err(|err| Error::MetaStore(err.into()))? .is_some()) } +} +impl HummockManager { /// Release invalid contexts, aka worker node ids which are no longer valid in `ClusterManager`. - #[named] pub(super) async fn release_invalid_contexts(&self) -> Result> { - let active_context_ids = { - let compaction_guard = read_lock!(self, compaction).await; - let versioning_guard = read_lock!(self, versioning).await; - let _timer = start_measure_real_process_timer!(self); + let (active_context_ids, mut context_info) = { + let compaction_guard = self.compaction.read().await; + let context_info = self.context_info.write().await; + let _timer = start_measure_real_process_timer!(self, "release_invalid_contexts"); let mut active_context_ids = HashSet::new(); active_context_ids.extend( compaction_guard @@ -100,19 +172,24 @@ impl HummockManager { .values() .map(|c| c.context_id), ); - active_context_ids.extend(versioning_guard.pinned_versions.keys()); - active_context_ids.extend(versioning_guard.pinned_snapshots.keys()); - active_context_ids + active_context_ids.extend(context_info.pinned_versions.keys()); + active_context_ids.extend(context_info.pinned_snapshots.keys()); + (active_context_ids, context_info) }; let mut invalid_context_ids = vec![]; for active_context_id in &active_context_ids { - if !self.check_context(*active_context_id).await? { + if !context_info + .check_context(*active_context_id, &self.metadata_manager) + .await? + { invalid_context_ids.push(*active_context_id); } } - self.release_contexts(&invalid_context_ids).await?; + context_info + .release_contexts(&invalid_context_ids, self.env.meta_store()) + .await?; Ok(invalid_context_ids) } @@ -133,7 +210,13 @@ impl HummockManager { continue; } } - if !self.check_context(*context_id).await? { + if !self + .context_info + .read() + .await + .check_context(*context_id, &self.metadata_manager) + .await? + { return Err(Error::InvalidSst(*sst_id)); } } @@ -184,3 +267,235 @@ impl HummockManager { self.release_contexts([META_NODE_ID]).await } } + +// pin and unpin method +impl HummockManager { + /// Pin the current greatest hummock version. The pin belongs to `context_id` + /// and will be unpinned when `context_id` is invalidated. + pub async fn pin_version(&self, context_id: HummockContextId) -> Result { + let versioning = self.versioning.read().await; + let mut context_info = self.context_info.write().await; + self.check_context_with_meta_node(context_id, &context_info) + .await?; + let _timer = start_measure_real_process_timer!(self, "pin_version"); + let mut pinned_versions = BTreeMapTransaction::new(&mut context_info.pinned_versions); + let mut context_pinned_version = pinned_versions.new_entry_txn_or_default( + context_id, + HummockPinnedVersion { + context_id, + min_pinned_id: INVALID_VERSION_ID, + }, + ); + let version_id = versioning.current_version.id; + let ret = versioning.current_version.clone(); + if context_pinned_version.min_pinned_id == INVALID_VERSION_ID + || context_pinned_version.min_pinned_id > version_id + { + context_pinned_version.min_pinned_id = version_id; + commit_multi_var!(self.meta_store_ref(), context_pinned_version)?; + trigger_pin_unpin_version_state(&self.metrics, &context_info.pinned_versions); + } + + #[cfg(test)] + { + drop(context_info); + drop(versioning); + self.check_state_consistency().await; + } + + Ok(ret) + } + + /// Unpin all pins which belongs to `context_id` and has an id which is older than + /// `unpin_before`. All versions >= `unpin_before` will be treated as if they are all pinned by + /// this `context_id` so they will not be vacuumed. + pub async fn unpin_version_before( + &self, + context_id: HummockContextId, + unpin_before: HummockVersionId, + ) -> Result<()> { + let mut context_info = self.context_info.write().await; + self.check_context_with_meta_node(context_id, &context_info) + .await?; + let _timer = start_measure_real_process_timer!(self, "unpin_version_before"); + let mut pinned_versions = BTreeMapTransaction::new(&mut context_info.pinned_versions); + let mut context_pinned_version = pinned_versions.new_entry_txn_or_default( + context_id, + HummockPinnedVersion { + context_id, + min_pinned_id: 0, + }, + ); + assert!( + context_pinned_version.min_pinned_id <= unpin_before, + "val must be monotonically non-decreasing. old = {}, new = {}.", + context_pinned_version.min_pinned_id, + unpin_before + ); + context_pinned_version.min_pinned_id = unpin_before; + commit_multi_var!(self.meta_store_ref(), context_pinned_version)?; + trigger_pin_unpin_version_state(&self.metrics, &context_info.pinned_versions); + + #[cfg(test)] + { + drop(context_info); + self.check_state_consistency().await; + } + + Ok(()) + } + + pub async fn pin_specific_snapshot( + &self, + context_id: HummockContextId, + epoch: HummockEpoch, + ) -> Result { + let snapshot = self.latest_snapshot.load(); + let mut guard = self.context_info.write().await; + self.check_context_with_meta_node(context_id, &guard) + .await?; + let mut pinned_snapshots = BTreeMapTransaction::new(&mut guard.pinned_snapshots); + let mut context_pinned_snapshot = pinned_snapshots.new_entry_txn_or_default( + context_id, + HummockPinnedSnapshot { + context_id, + minimal_pinned_snapshot: INVALID_EPOCH, + }, + ); + let epoch_to_pin = std::cmp::min(epoch, snapshot.committed_epoch); + if context_pinned_snapshot.minimal_pinned_snapshot == INVALID_EPOCH { + context_pinned_snapshot.minimal_pinned_snapshot = epoch_to_pin; + commit_multi_var!(self.meta_store_ref(), context_pinned_snapshot)?; + } + Ok(HummockSnapshot::clone(&snapshot)) + } + + /// Make sure `max_committed_epoch` is pinned and return it. + pub async fn pin_snapshot(&self, context_id: HummockContextId) -> Result { + let snapshot = self.latest_snapshot.load(); + let mut guard = self.context_info.write().await; + self.check_context_with_meta_node(context_id, &guard) + .await?; + let _timer = start_measure_real_process_timer!(self, "pin_snapshot"); + let mut pinned_snapshots = BTreeMapTransaction::new(&mut guard.pinned_snapshots); + let mut context_pinned_snapshot = pinned_snapshots.new_entry_txn_or_default( + context_id, + HummockPinnedSnapshot { + context_id, + minimal_pinned_snapshot: INVALID_EPOCH, + }, + ); + if context_pinned_snapshot.minimal_pinned_snapshot == INVALID_EPOCH { + context_pinned_snapshot.minimal_pinned_snapshot = snapshot.committed_epoch; + commit_multi_var!(self.meta_store_ref(), context_pinned_snapshot)?; + trigger_pin_unpin_snapshot_state(&self.metrics, &guard.pinned_snapshots); + } + Ok(HummockSnapshot::clone(&snapshot)) + } + + pub async fn unpin_snapshot(&self, context_id: HummockContextId) -> Result<()> { + let mut context_info = self.context_info.write().await; + self.check_context_with_meta_node(context_id, &context_info) + .await?; + let _timer = start_measure_real_process_timer!(self, "unpin_snapshot"); + let mut pinned_snapshots = BTreeMapTransaction::new(&mut context_info.pinned_snapshots); + let release_snapshot = pinned_snapshots.remove(context_id); + if release_snapshot.is_some() { + commit_multi_var!(self.meta_store_ref(), pinned_snapshots)?; + trigger_pin_unpin_snapshot_state(&self.metrics, &context_info.pinned_snapshots); + } + + #[cfg(test)] + { + drop(context_info); + self.check_state_consistency().await; + } + + Ok(()) + } + + /// Unpin all snapshots smaller than specified epoch for current context. + pub async fn unpin_snapshot_before( + &self, + context_id: HummockContextId, + hummock_snapshot: HummockSnapshot, + ) -> Result<()> { + let versioning = self.versioning.read().await; + let mut context_info = self.context_info.write().await; + self.check_context_with_meta_node(context_id, &context_info) + .await?; + let _timer = start_measure_real_process_timer!(self, "unpin_snapshot_before"); + // Use the max_committed_epoch in storage as the snapshot ts so only committed changes are + // visible in the snapshot. + let max_committed_epoch = versioning.current_version.max_committed_epoch; + // Ensure the unpin will not clean the latest one. + let snapshot_committed_epoch = hummock_snapshot.committed_epoch; + #[cfg(not(test))] + { + assert!(snapshot_committed_epoch <= max_committed_epoch); + } + let last_read_epoch = std::cmp::min(snapshot_committed_epoch, max_committed_epoch); + + let mut pinned_snapshots = BTreeMapTransaction::new(&mut context_info.pinned_snapshots); + let mut context_pinned_snapshot = pinned_snapshots.new_entry_txn_or_default( + context_id, + HummockPinnedSnapshot { + context_id, + minimal_pinned_snapshot: INVALID_EPOCH, + }, + ); + + // Unpin the snapshots pinned by meta but frontend doesn't know. Also equal to unpin all + // epochs below specific watermark. + if context_pinned_snapshot.minimal_pinned_snapshot < last_read_epoch + || context_pinned_snapshot.minimal_pinned_snapshot == INVALID_EPOCH + { + context_pinned_snapshot.minimal_pinned_snapshot = last_read_epoch; + commit_multi_var!(self.meta_store_ref(), context_pinned_snapshot)?; + trigger_pin_unpin_snapshot_state(&self.metrics, &context_info.pinned_snapshots); + } + + #[cfg(test)] + { + drop(context_info); + drop(versioning); + self.check_state_consistency().await; + } + + Ok(()) + } +} + +// safe point +impl HummockManager { + pub async fn register_safe_point(&self) -> HummockVersionSafePoint { + let versioning = self.versioning.read().await; + let mut wl = self.context_info.write().await; + let safe_point = HummockVersionSafePoint { + id: versioning.current_version.id, + event_sender: self.event_sender.clone(), + }; + wl.version_safe_points.push(safe_point.id); + trigger_safepoint_stat(&self.metrics, &wl.version_safe_points); + safe_point + } + + pub async fn unregister_safe_point(&self, safe_point: HummockVersionId) { + let mut wl = self.context_info.write().await; + let version_safe_points = &mut wl.version_safe_points; + if let Some(pos) = version_safe_points.iter().position(|sp| *sp == safe_point) { + version_safe_points.remove(pos); + } + trigger_safepoint_stat(&self.metrics, &wl.version_safe_points); + } +} + +fn trigger_safepoint_stat(metrics: &MetaMetrics, safepoints: &[HummockVersionId]) { + if let Some(sp) = safepoints.iter().min() { + metrics.min_safepoint_version_id.set(*sp as _); + } else { + metrics + .min_safepoint_version_id + .set(HummockVersionId::MAX as _); + } +} diff --git a/src/meta/src/hummock/manager/gc.rs b/src/meta/src/hummock/manager/gc.rs index dafd3231afcce..5f5150b7777cb 100644 --- a/src/meta/src/hummock/manager/gc.rs +++ b/src/meta/src/hummock/manager/gc.rs @@ -19,9 +19,9 @@ use std::ops::DerefMut; use std::time::Duration; use anyhow::Context; -use function_name::named; use futures::{stream, StreamExt}; use itertools::Itertools; +use parking_lot::Mutex; use risingwave_hummock_sdk::HummockSstableObjectId; use risingwave_pb::common::worker_node::State::Running; use risingwave_pb::common::WorkerType; @@ -29,32 +29,53 @@ use risingwave_pb::hummock::subscribe_compaction_event_response::Event as Respon use risingwave_pb::hummock::FullScanTask; use crate::hummock::error::{Error, Result}; -use crate::hummock::manager::versioning::Versioning; -use crate::hummock::manager::{commit_multi_var, create_trx_wrapper, read_lock, write_lock}; +use crate::hummock::manager::commit_multi_var; use crate::hummock::HummockManager; use crate::manager::MetadataManager; -use crate::model::{BTreeMapTransaction, BTreeMapTransactionWrapper, ValTransaction}; -use crate::storage::MetaStore; +use crate::model::BTreeMapTransaction; + +#[derive(Default)] +pub(super) struct DeleteObjectTracker { + /// Objects that waits to be deleted from object store. It comes from either compaction, or + /// full GC (listing object store). + objects_to_delete: Mutex>, +} + +impl DeleteObjectTracker { + pub(super) fn add(&self, objects: impl Iterator) { + self.objects_to_delete.lock().extend(objects) + } + + pub(super) fn current(&self) -> HashSet { + self.objects_to_delete.lock().clone() + } + + pub(super) fn clear(&self) { + self.objects_to_delete.lock().clear(); + } + + pub(super) fn ack<'a>(&self, objects: impl Iterator) { + let mut lock = self.objects_to_delete.lock(); + for object in objects { + lock.remove(object); + } + } +} impl HummockManager { /// Gets SST objects that is safe to be deleted from object store. - #[named] - pub async fn get_objects_to_delete(&self) -> Vec { - read_lock!(self, versioning) - .await - .objects_to_delete + pub fn get_objects_to_delete(&self) -> Vec { + self.delete_object_tracker + .current() .iter() .cloned() .collect_vec() } /// Acknowledges SSTs have been deleted from object store. - #[named] pub async fn ack_deleted_objects(&self, object_ids: &[HummockSstableObjectId]) -> Result<()> { - let mut versioning_guard = write_lock!(self, versioning).await; - for object_id in object_ids { - versioning_guard.objects_to_delete.remove(object_id); - } + self.delete_object_tracker.ack(object_ids.iter()); + let mut versioning_guard = self.versioning.write().await; for stale_objects in versioning_guard.checkpoint.stale_objects.values_mut() { stale_objects.id.retain(|id| !object_ids.contains(id)); } @@ -69,10 +90,10 @@ impl HummockManager { /// Deletes at most `batch_size` deltas. /// /// Returns (number of deleted deltas, number of remain `deltas_to_delete`). - #[named] pub async fn delete_version_deltas(&self, batch_size: usize) -> Result<(usize, usize)> { - let mut versioning_guard = write_lock!(self, versioning).await; + let mut versioning_guard = self.versioning.write().await; let versioning = versioning_guard.deref_mut(); + let context_info = self.context_info.read().await; let deltas_to_delete = versioning .hummock_version_deltas .range(..=versioning.checkpoint.version.id) @@ -80,14 +101,11 @@ impl HummockManager { .collect_vec(); // If there is any safe point, skip this to ensure meta backup has required delta logs to // replay version. - if !versioning.version_safe_points.is_empty() { + if !context_info.version_safe_points.is_empty() { return Ok((0, deltas_to_delete.len())); } - let mut hummock_version_deltas = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.hummock_version_deltas,) - ); + let mut hummock_version_deltas = + BTreeMapTransaction::new(&mut versioning.hummock_version_deltas); let batch = deltas_to_delete .iter() .take(batch_size) @@ -102,6 +120,7 @@ impl HummockManager { commit_multi_var!(self.meta_store_ref(), hummock_version_deltas)?; #[cfg(test)] { + drop(context_info); drop(versioning_guard); self.check_state_consistency().await; } @@ -110,16 +129,15 @@ impl HummockManager { /// Extends `objects_to_delete` according to object store full scan result. /// Caller should ensure `object_ids` doesn't include any SST objects belong to a on-going - /// version write. That's to say, these object_ids won't appear in either `commit_epoch` or + /// version write. That's to say, these `object_ids` won't appear in either `commit_epoch` or /// `report_compact_task`. - #[named] pub async fn extend_objects_to_delete_from_scan( &self, object_ids: &[HummockSstableObjectId], ) -> usize { let tracked_object_ids: HashSet = { - let versioning_guard = read_lock!(self, versioning).await; - let versioning: &Versioning = &versioning_guard; + let versioning = self.versioning.read().await; + let context_info = self.context_info.read().await; // object ids in checkpoint version let mut tracked_object_ids = versioning.checkpoint.version.get_object_ids(); @@ -131,7 +149,7 @@ impl HummockManager { tracked_object_ids.extend(delta.newly_added_object_ids()); } // add stale object ids before the checkpoint version - let min_pinned_version_id = versioning.min_pinned_version_id(); + let min_pinned_version_id = context_info.min_pinned_version_id(); tracked_object_ids.extend( versioning .checkpoint @@ -147,16 +165,15 @@ impl HummockManager { .iter() .filter(|object_id| !tracked_object_ids.contains(object_id)) .collect_vec(); - let mut versioning_guard = write_lock!(self, versioning).await; - versioning_guard.objects_to_delete.extend(to_delete.clone()); - drop(versioning_guard); + self.delete_object_tracker + .add(to_delete.iter().map(|id| **id)); to_delete.len() } /// Starts a full GC. /// 1. Meta node sends a `FullScanTask` to a compactor in this method. /// 2. The compactor returns scan result of object store to meta node. See - /// `HummockManager::full_scan_inner` in storage crate. + /// `HummockManager::full_scan_inner` in storage crate. /// 3. Meta node decides which SSTs to delete. See `HummockManager::complete_full_gc`. /// /// Returns Ok(false) if there is no worker available. @@ -300,7 +317,7 @@ mod tests { use itertools::Itertools; use risingwave_hummock_sdk::HummockSstableObjectId; - use crate::hummock::manager::ResponseEvent; + use super::ResponseEvent; use crate::hummock::test_utils::{add_test_tables, setup_compute_env}; use crate::MetaOpts; diff --git a/src/meta/src/hummock/manager/mod.rs b/src/meta/src/hummock/manager/mod.rs index 0dc940e006584..65eede718c5c4 100644 --- a/src/meta/src/hummock/manager/mod.rs +++ b/src/meta/src/hummock/manager/mod.rs @@ -12,113 +12,66 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::borrow::BorrowMut; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeMap, HashMap, VecDeque}; use std::ops::{Deref, DerefMut}; use std::sync::atomic::AtomicBool; -use std::sync::{Arc, LazyLock}; -use std::time::{Duration, Instant, SystemTime}; +use std::sync::Arc; -use anyhow::Context; use arc_swap::ArcSwap; use bytes::Bytes; -use fail::fail_point; -use function_name::named; -use futures::future::Either; -use futures::stream::{BoxStream, FuturesUnordered}; -use futures::{FutureExt, StreamExt}; use itertools::Itertools; -use parking_lot::Mutex; -use rand::prelude::SliceRandom; -use rand::thread_rng; -use risingwave_common::catalog::TableId; -use risingwave_common::config::default::compaction_config; use risingwave_common::monitor::rwlock::MonitoredRwLock; use risingwave_common::system_param::reader::SystemParamsRead; -use risingwave_common::util::epoch::{Epoch, INVALID_EPOCH}; -use risingwave_hummock_sdk::compact::{compact_task_to_string, statistics_compact_task}; -use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - build_version_delta_after_version, get_compaction_group_ids, - get_table_compaction_group_id_mapping, BranchedSstInfo, HummockLevelsExt, -}; -use risingwave_hummock_sdk::version::HummockVersionDelta; +use risingwave_common::util::epoch::INVALID_EPOCH; +use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta}; use risingwave_hummock_sdk::{ - version_archive_dir, version_checkpoint_path, CompactionGroupId, ExtendedSstableInfo, - HummockCompactionTaskId, HummockContextId, HummockEpoch, HummockSstableId, - HummockSstableObjectId, HummockVersionId, SstObjectIdRange, INVALID_VERSION_ID, + version_archive_dir, version_checkpoint_path, CompactionGroupId, HummockCompactionTaskId, + HummockContextId, HummockVersionId, }; use risingwave_meta_model_v2::{ compaction_status, compaction_task, hummock_pinned_snapshot, hummock_pinned_version, hummock_version_delta, hummock_version_stats, }; -use risingwave_pb::hummock::compact_task::{self, TaskStatus, TaskType}; -use risingwave_pb::hummock::group_delta::DeltaType; -use risingwave_pb::hummock::rise_ctl_update_compaction_config_request::mutable_config; -use risingwave_pb::hummock::subscribe_compaction_event_request::{ - Event as RequestEvent, HeartBeat, ReportTask, -}; -use risingwave_pb::hummock::subscribe_compaction_event_response::Event as ResponseEvent; use risingwave_pb::hummock::{ - CompactTask, CompactTaskAssignment, GroupDelta, GroupMetaChange, HummockPinnedSnapshot, - HummockPinnedVersion, HummockSnapshot, HummockVersionStats, IntraLevelDelta, - PbCompactionGroupInfo, SstableInfo, SubscribeCompactionEventRequest, TableOption, TableSchema, + CompactTaskAssignment, HummockPinnedSnapshot, HummockPinnedVersion, HummockSnapshot, + HummockVersionStats, PbCompactionGroupInfo, SubscribeCompactionEventRequest, }; -use risingwave_pb::meta::subscribe_response::{Info, Operation}; -use rw_futures_util::{pending_on_none, select_all}; -use thiserror_ext::AsReport; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; -use tokio::sync::oneshot::Sender; -use tokio::sync::RwLockWriteGuard; -use tokio::task::JoinHandle; -use tokio_stream::wrappers::IntervalStream; +use risingwave_pb::meta::subscribe_response::Operation; +use tokio::sync::mpsc::UnboundedSender; use tonic::Streaming; -use tracing::warn; -use crate::hummock::compaction::selector::{ - DynamicLevelSelector, LocalSelectorStatistic, ManualCompactionOption, ManualCompactionSelector, - SpaceReclaimCompactionSelector, TombstoneCompactionSelector, TtlCompactionSelector, -}; -use crate::hummock::compaction::{CompactStatus, CompactionDeveloperConfig}; -use crate::hummock::error::{Error, Result}; -use crate::hummock::metrics_utils::{ - build_compact_task_level_type_metrics_label, get_or_create_local_table_stat, - trigger_delta_log_stats, trigger_local_table_stat, trigger_lsm_stat, trigger_mv_stat, - trigger_pin_unpin_snapshot_state, trigger_pin_unpin_version_state, trigger_split_stat, - trigger_sst_stat, trigger_version_stat, trigger_write_stop_stats, -}; -use crate::hummock::sequence::next_compaction_task_id; -use crate::hummock::{CompactorManagerRef, TASK_NORMAL}; -#[cfg(any(test, feature = "test"))] -use crate::manager::{ClusterManagerRef, FragmentManagerRef}; -use crate::manager::{MetaSrvEnv, MetaStoreImpl, MetadataManager, META_NODE_ID}; -use crate::model::{ - BTreeMapEntryTransaction, BTreeMapEntryTransactionWrapper, BTreeMapTransaction, - BTreeMapTransactionWrapper, ClusterId, MetadataModel, MetadataModelError, ValTransaction, - VarTransaction, VarTransactionWrapper, -}; +use crate::hummock::compaction::CompactStatus; +use crate::hummock::error::Result; +use crate::hummock::manager::checkpoint::HummockVersionCheckpoint; +use crate::hummock::manager::context::ContextInfo; +use crate::hummock::manager::gc::DeleteObjectTracker; +use crate::hummock::CompactorManagerRef; +use crate::manager::{MetaSrvEnv, MetaStoreImpl, MetadataManager}; +use crate::model::{ClusterId, MetadataModel, MetadataModelError}; use crate::rpc::metrics::MetaMetrics; -use crate::storage::MetaStore; mod compaction_group_manager; mod context; mod gc; -#[cfg(test)] mod tests; mod versioning; -pub use versioning::HummockVersionSafePoint; +pub use context::HummockVersionSafePoint; use versioning::*; pub(crate) mod checkpoint; +mod commit_epoch; mod compaction; pub mod sequence; +mod timer_task; +mod transaction; mod utils; mod worker; +pub(crate) use commit_epoch::*; use compaction::*; pub use compaction::{check_cg_write_limit, WriteLimitType}; pub(crate) use utils::*; type Snapshot = ArcSwap; -const HISTORY_TABLE_INFO_STATISTIC_TIME: usize = 240; // Update to states are performed as follow: // - Initialize ValTransaction for the meta state to update @@ -129,13 +82,14 @@ pub struct HummockManager { pub env: MetaSrvEnv, metadata_manager: MetadataManager, - /// Lock order: compaction, versioning, `compaction_group_manager`. - /// - Lock compaction first, then versioning, and finally `compaction_group_manager`. + /// Lock order: `compaction`, `versioning`, `compaction_group_manager`, `context_info` + /// - Lock `compaction` first, then `versioning`, then `compaction_group_manager` and finally `context_info`. /// - This order should be strictly followed to prevent deadlock. compaction: MonitoredRwLock, versioning: MonitoredRwLock, /// `CompactionGroupManager` manages compaction configs for compaction groups. - compaction_group_manager: tokio::sync::RwLock, + compaction_group_manager: MonitoredRwLock, + context_info: MonitoredRwLock, latest_snapshot: Snapshot, pub metrics: Arc, @@ -143,6 +97,8 @@ pub struct HummockManager { pub compactor_manager: CompactorManagerRef, event_sender: HummockManagerEventSender, + delete_object_tracker: DeleteObjectTracker, + object_store: ObjectStoreRef, version_checkpoint_path: String, version_archive_dir: String, @@ -157,69 +113,21 @@ pub struct HummockManager { // `compaction_state` will record the types of compact tasks that can be triggered in `hummock` // and suggest types with a certain priority. pub compaction_state: CompactionState, - - // Record the partition corresponding to the table in each group (accepting delays) - // The compactor will refer to this structure to determine how to cut the boundaries of sst. - // Currently, we update it in a couple of scenarios - // 1. throughput and size are checked periodically and calculated according to the rules - // 2. A new group is created (split) - // 3. split_weight_by_vnode is modified for an existing group. (not supported yet) - // Tips: - // 1. When table_id does not exist in the current structure, compactor will not cut the boundary - // 2. When partition count <=1, compactor will still use table_id as the cutting boundary of sst - // 3. Modify the special configuration item hybrid_vnode_count = 0 to remove the table_id in hybrid cg and no longer perform alignment cutting. - group_to_table_vnode_partition: - parking_lot::RwLock>>, } pub type HummockManagerRef = Arc; -/// Acquire read lock of the lock with `lock_name`. -/// The macro will use macro `function_name` to get the name of the function of method that calls -/// the lock, and therefore, anyone to call this macro should ensured that the caller method has the -/// macro #[named] -macro_rules! read_lock { - ($hummock_mgr:expr, $lock_name:ident) => { - async { - $hummock_mgr - .$lock_name - .read(&[function_name!(), stringify!($lock_name), "read"]) - .await - } - }; -} -pub(crate) use read_lock; use risingwave_hummock_sdk::compaction_group::{StateTableId, StaticCompactionGroupId}; -use risingwave_hummock_sdk::table_stats::{ - add_prost_table_stats_map, purge_prost_table_stats, PbTableStatsMap, -}; use risingwave_object_store::object::{build_remote_object_store, ObjectError, ObjectStoreRef}; use risingwave_pb::catalog::Table; -use risingwave_pb::hummock::level_handler::RunningCompactTask; use risingwave_pb::meta::relation::RelationInfo; -/// Acquire write lock of the lock with `lock_name`. -/// The macro will use macro `function_name` to get the name of the function of method that calls -/// the lock, and therefore, anyone to call this macro should ensured that the caller method has the -/// macro #[named] -macro_rules! write_lock { - ($hummock_mgr:expr, $lock_name:ident) => { - async { - $hummock_mgr - .$lock_name - .write(&[function_name!(), stringify!($lock_name), "write"]) - .await - } - }; -} -pub(crate) use write_lock; - macro_rules! start_measure_real_process_timer { - ($hummock_mgr:expr) => { + ($hummock_mgr:expr, $func_name:literal) => { $hummock_mgr .metrics .hummock_manager_real_process_time - .with_label_values(&[function_name!()]) + .with_label_values(&[$func_name]) .start_timer() }; } @@ -228,72 +136,6 @@ pub(crate) use start_measure_real_process_timer; use crate::hummock::manager::compaction_group_manager::CompactionGroupManager; use crate::hummock::manager::worker::HummockManagerEventSender; -pub static CANCEL_STATUS_SET: LazyLock> = LazyLock::new(|| { - [ - TaskStatus::ManualCanceled, - TaskStatus::SendFailCanceled, - TaskStatus::AssignFailCanceled, - TaskStatus::HeartbeatCanceled, - TaskStatus::InvalidGroupCanceled, - TaskStatus::NoAvailMemoryResourceCanceled, - TaskStatus::NoAvailCpuResourceCanceled, - ] - .into_iter() - .collect() -}); - -#[derive(Debug, Clone)] -pub struct NewTableFragmentInfo { - pub table_id: TableId, - pub mv_table_id: Option, - pub internal_table_ids: Vec, -} - -impl NewTableFragmentInfo { - pub fn state_table_ids(&self) -> impl Iterator + '_ { - self.mv_table_id - .iter() - .chain(self.internal_table_ids.iter()) - .cloned() - } -} - -pub struct CommitEpochInfo { - pub sstables: Vec, - pub new_table_watermarks: HashMap, - pub sst_to_context: HashMap, - pub new_table_fragment_info: Option, -} - -impl CommitEpochInfo { - pub fn new( - sstables: Vec, - new_table_watermarks: HashMap, - sst_to_context: HashMap, - new_table_fragment_info: Option, - ) -> Self { - Self { - sstables, - new_table_watermarks, - sst_to_context, - new_table_fragment_info, - } - } - - #[cfg(any(test, feature = "test"))] - pub(crate) fn for_test( - sstables: Vec>, - sst_to_context: HashMap, - ) -> Self { - Self::new( - sstables.into_iter().map(Into::into).collect(), - HashMap::new(), - sst_to_context, - None, - ) - } -} - impl HummockManager { pub async fn new( env: MetaSrvEnv, @@ -305,7 +147,7 @@ impl HummockManager { Streaming, )>, ) -> Result { - let compaction_group_manager = Self::build_compaction_group_manager(&env).await?; + let compaction_group_manager = CompactionGroupManager::new(&env).await?; Self::new_impl( env, metadata_manager, @@ -320,8 +162,8 @@ impl HummockManager { #[cfg(any(test, feature = "test"))] pub(super) async fn with_config( env: MetaSrvEnv, - cluster_manager: ClusterManagerRef, - fragment_manager: FragmentManagerRef, + cluster_manager: crate::manager::ClusterManagerRef, + fragment_manager: crate::manager::FragmentManagerRef, metrics: Arc, compactor_manager: CompactorManagerRef, config: risingwave_pb::hummock::CompactionConfig, @@ -331,10 +173,9 @@ impl HummockManager { )>, ) -> HummockManagerRef { use crate::manager::CatalogManager; - let compaction_group_manager = - Self::build_compaction_group_manager_with_config(&env, config) - .await - .unwrap(); + let compaction_group_manager = CompactionGroupManager::new_with_config(&env, config) + .await + .unwrap(); let catalog_manager = Arc::new(CatalogManager::new(env.clone()).await.unwrap()); let metadata_manager = MetadataManager::new_v1(cluster_manager, catalog_manager, fragment_manager); @@ -355,7 +196,7 @@ impl HummockManager { metadata_manager: MetadataManager, metrics: Arc, compactor_manager: CompactorManagerRef, - compaction_group_manager: tokio::sync::RwLock, + compaction_group_manager: CompactionGroupManager, compactor_streams_change_tx: UnboundedSender<( u32, Streaming, @@ -374,7 +215,7 @@ impl HummockManager { state_store_url.strip_prefix("hummock+").unwrap_or("memory"), metrics.object_store_metric.clone(), "Version Checkpoint", - object_store_config, + Arc::new(object_store_config), ) .await, ); @@ -411,14 +252,25 @@ impl HummockManager { versioning: MonitoredRwLock::new( metrics.hummock_manager_lock_time.clone(), Default::default(), + "hummock_manager::versioning", ), compaction: MonitoredRwLock::new( metrics.hummock_manager_lock_time.clone(), Default::default(), + "hummock_manager::compaction", + ), + compaction_group_manager: MonitoredRwLock::new( + metrics.hummock_manager_lock_time.clone(), + compaction_group_manager, + "hummock_manager::compaction_group_manager", + ), + context_info: MonitoredRwLock::new( + metrics.hummock_manager_lock_time.clone(), + Default::default(), + "hummock_manager::context_info", ), metrics, metadata_manager, - compaction_group_manager, // compaction_request_channel: parking_lot::RwLock::new(None), compactor_manager, latest_snapshot: ArcSwap::from_pointee(HummockSnapshot { @@ -426,6 +278,7 @@ impl HummockManager { current_epoch: INVALID_EPOCH, }), event_sender: tx, + delete_object_tracker: Default::default(), object_store, version_checkpoint_path, version_archive_dir, @@ -433,7 +286,6 @@ impl HummockManager { history_table_throughput: parking_lot::RwLock::new(HashMap::default()), compactor_streams_change_tx, compaction_state: CompactionState::new(), - group_to_table_vnode_partition: parking_lot::RwLock::new(HashMap::default()), }; let instance = Arc::new(instance); instance.start_worker(rx).await; @@ -444,18 +296,19 @@ impl HummockManager { Ok(instance) } - fn meta_store_ref(&self) -> MetaStoreImpl { + fn meta_store_ref(&self) -> &MetaStoreImpl { self.env.meta_store_ref() } /// Load state from meta store. - #[named] async fn load_meta_store_state(&self) -> Result<()> { - let mut compaction_guard = write_lock!(self, compaction).await; - let mut versioning_guard = write_lock!(self, versioning).await; + let mut compaction_guard = self.compaction.write().await; + let mut versioning_guard = self.versioning.write().await; + let mut context_info_guard = self.context_info.write().await; self.load_meta_store_state_impl( - compaction_guard.borrow_mut(), - versioning_guard.borrow_mut(), + &mut compaction_guard, + &mut versioning_guard, + &mut context_info_guard, ) .await } @@ -463,8 +316,9 @@ impl HummockManager { /// Load state from meta store. async fn load_meta_store_state_impl( &self, - compaction_guard: &mut RwLockWriteGuard<'_, Compaction>, - versioning_guard: &mut RwLockWriteGuard<'_, Versioning>, + compaction_guard: &mut Compaction, + versioning_guard: &mut Versioning, + context_info: &mut ContextInfo, ) -> Result<()> { use sea_orm::EntityTrait; let meta_store = self.meta_store_ref(); @@ -537,7 +391,7 @@ impl HummockManager { .read() .await .default_compaction_config(); - let checkpoint_version = create_init_version(default_compaction_config); + let checkpoint_version = HummockVersion::create_init_version(default_compaction_config); tracing::info!("init hummock version checkpoint"); versioning_guard.checkpoint = HummockVersionCheckpoint { version: checkpoint_version.clone(), @@ -576,10 +430,9 @@ impl HummockManager { .into(), ); versioning_guard.current_version = redo_state; - versioning_guard.branched_ssts = versioning_guard.current_version.build_branched_sst_info(); versioning_guard.hummock_version_deltas = hummock_version_deltas; - versioning_guard.pinned_versions = match &meta_store { + context_info.pinned_versions = match &meta_store { MetaStoreImpl::Kv(meta_store) => HummockPinnedVersion::list(meta_store) .await? .into_iter() @@ -594,7 +447,7 @@ impl HummockManager { .collect(), }; - versioning_guard.pinned_snapshots = match &meta_store { + context_info.pinned_snapshots = match &meta_store { MetaStoreImpl::Kv(meta_store) => HummockPinnedSnapshot::list(meta_store) .await? .into_iter() @@ -609,2925 +462,138 @@ impl HummockManager { .collect(), }; - versioning_guard.objects_to_delete.clear(); + self.delete_object_tracker.clear(); // Not delete stale objects when archive is enabled if !self.env.opts.enable_hummock_data_archive { - versioning_guard.mark_objects_for_deletion(); + versioning_guard.mark_objects_for_deletion(context_info, &self.delete_object_tracker); } - self.initial_compaction_group_config_after_load(versioning_guard) - .await?; - - Ok(()) - } + self.initial_compaction_group_config_after_load( + versioning_guard, + self.compaction_group_manager.write().await.deref_mut(), + ) + .await?; - /// Caller should hold `versioning` lock, to sync with `HummockManager::release_contexts`. - async fn check_context_with_meta_node(&self, context_id: HummockContextId) -> Result<()> { - if context_id == META_NODE_ID { - // Using the preserved meta id is allowed. - } else if !self.check_context(context_id).await? { - // The worker is not found in cluster. - return Err(Error::InvalidContext(context_id)); - } Ok(()) } - /// Pin the current greatest hummock version. The pin belongs to `context_id` - /// and will be unpinned when `context_id` is invalidated. - #[named] - pub async fn pin_version(&self, context_id: HummockContextId) -> Result { - let mut versioning_guard = write_lock!(self, versioning).await; - self.check_context_with_meta_node(context_id).await?; - let _timer = start_measure_real_process_timer!(self); - let versioning = versioning_guard.deref_mut(); - let mut pinned_versions = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.pinned_versions,) - ); - let mut context_pinned_version = pinned_versions.new_entry_txn_or_default( - context_id, - HummockPinnedVersion { - context_id, - min_pinned_id: INVALID_VERSION_ID, - }, - ); - let version_id = versioning.current_version.id; - let ret = versioning.current_version.clone(); - if context_pinned_version.min_pinned_id == INVALID_VERSION_ID - || context_pinned_version.min_pinned_id > version_id - { - context_pinned_version.min_pinned_id = version_id; - commit_multi_var!(self.meta_store_ref(), context_pinned_version)?; - trigger_pin_unpin_version_state(&self.metrics, &versioning.pinned_versions); - } - - #[cfg(test)] - { - drop(versioning_guard); - self.check_state_consistency().await; - } - - Ok(ret) - } - - /// Unpin all pins which belongs to `context_id` and has an id which is older than - /// `unpin_before`. All versions >= `unpin_before` will be treated as if they are all pinned by - /// this `context_id` so they will not be vacuumed. - #[named] - pub async fn unpin_version_before( + pub async fn init_metadata_for_version_replay( &self, - context_id: HummockContextId, - unpin_before: HummockVersionId, + table_catalogs: Vec

, + compaction_groups: Vec, ) -> Result<()> { - let mut versioning_guard = write_lock!(self, versioning).await; - self.check_context_with_meta_node(context_id).await?; - let _timer = start_measure_real_process_timer!(self); - let versioning = versioning_guard.deref_mut(); - let mut pinned_versions = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.pinned_versions,) - ); - let mut context_pinned_version = pinned_versions.new_entry_txn_or_default( - context_id, - HummockPinnedVersion { - context_id, - min_pinned_id: 0, - }, - ); - assert!( - context_pinned_version.min_pinned_id <= unpin_before, - "val must be monotonically non-decreasing. old = {}, new = {}.", - context_pinned_version.min_pinned_id, - unpin_before - ); - context_pinned_version.min_pinned_id = unpin_before; - commit_multi_var!(self.meta_store_ref(), context_pinned_version)?; - trigger_pin_unpin_version_state(&self.metrics, &versioning.pinned_versions); - - #[cfg(test)] - { - drop(versioning_guard); - self.check_state_consistency().await; - } - - Ok(()) - } - - #[named] - pub async fn pin_specific_snapshot( - &self, - context_id: HummockContextId, - epoch: HummockEpoch, - ) -> Result { - let snapshot = self.latest_snapshot.load(); - let mut guard = write_lock!(self, versioning).await; - self.check_context_with_meta_node(context_id).await?; - let mut pinned_snapshots = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut guard.pinned_snapshots,) - ); - let mut context_pinned_snapshot = pinned_snapshots.new_entry_txn_or_default( - context_id, - HummockPinnedSnapshot { - context_id, - minimal_pinned_snapshot: INVALID_EPOCH, - }, - ); - let epoch_to_pin = std::cmp::min(epoch, snapshot.committed_epoch); - if context_pinned_snapshot.minimal_pinned_snapshot == INVALID_EPOCH { - context_pinned_snapshot.minimal_pinned_snapshot = epoch_to_pin; - commit_multi_var!(self.meta_store_ref(), context_pinned_snapshot)?; - } - Ok(HummockSnapshot::clone(&snapshot)) - } - - /// Make sure `max_committed_epoch` is pinned and return it. - #[named] - pub async fn pin_snapshot(&self, context_id: HummockContextId) -> Result { - let snapshot = self.latest_snapshot.load(); - let mut guard = write_lock!(self, versioning).await; - self.check_context_with_meta_node(context_id).await?; - let _timer = start_measure_real_process_timer!(self); - let mut pinned_snapshots = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut guard.pinned_snapshots,) - ); - let mut context_pinned_snapshot = pinned_snapshots.new_entry_txn_or_default( - context_id, - HummockPinnedSnapshot { - context_id, - minimal_pinned_snapshot: INVALID_EPOCH, - }, - ); - if context_pinned_snapshot.minimal_pinned_snapshot == INVALID_EPOCH { - context_pinned_snapshot.minimal_pinned_snapshot = snapshot.committed_epoch; - commit_multi_var!(self.meta_store_ref(), context_pinned_snapshot)?; - trigger_pin_unpin_snapshot_state(&self.metrics, &guard.pinned_snapshots); - } - Ok(HummockSnapshot::clone(&snapshot)) - } - - pub fn latest_snapshot(&self) -> HummockSnapshot { - let snapshot = self.latest_snapshot.load(); - HummockSnapshot::clone(&snapshot) - } - - #[named] - pub async fn unpin_snapshot(&self, context_id: HummockContextId) -> Result<()> { - let mut versioning_guard = write_lock!(self, versioning).await; - self.check_context_with_meta_node(context_id).await?; - let _timer = start_measure_real_process_timer!(self); - let mut pinned_snapshots = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning_guard.pinned_snapshots,) - ); - let release_snapshot = pinned_snapshots.remove(context_id); - if release_snapshot.is_some() { - commit_multi_var!(self.meta_store_ref(), pinned_snapshots)?; - trigger_pin_unpin_snapshot_state(&self.metrics, &versioning_guard.pinned_snapshots); + for table in &table_catalogs { + table.insert(self.env.meta_store().as_kv()).await?; } - - #[cfg(test)] - { - drop(versioning_guard); - self.check_state_consistency().await; + for group in &compaction_groups { + assert!( + group.id == StaticCompactionGroupId::NewCompactionGroup as u64 + || (group.id >= StaticCompactionGroupId::StateDefault as u64 + && group.id <= StaticCompactionGroupId::MaterializedView as u64), + "compaction group id should be either NewCompactionGroup to create new one, or predefined static ones." + ); } - Ok(()) - } + let mut compaction_group_manager = self.compaction_group_manager.write().await; + let mut compaction_groups_txn = compaction_group_manager.start_compaction_groups_txn(); + for group in &compaction_groups { + let mut pairs = vec![]; + for table_id in group.member_table_ids.clone() { + pairs.push((table_id as StateTableId, group.id)); + } + let group_config = group.compaction_config.clone().unwrap(); + compaction_groups_txn.create_compaction_groups(group.id, Arc::new(group_config)); - /// Unpin all snapshots smaller than specified epoch for current context. - #[named] - pub async fn unpin_snapshot_before( - &self, - context_id: HummockContextId, - hummock_snapshot: HummockSnapshot, - ) -> Result<()> { - let mut versioning_guard = write_lock!(self, versioning).await; - self.check_context_with_meta_node(context_id).await?; - let _timer = start_measure_real_process_timer!(self); - // Use the max_committed_epoch in storage as the snapshot ts so only committed changes are - // visible in the snapshot. - let max_committed_epoch = versioning_guard.current_version.max_committed_epoch; - // Ensure the unpin will not clean the latest one. - let snapshot_committed_epoch = hummock_snapshot.committed_epoch; - #[cfg(not(test))] - { - assert!(snapshot_committed_epoch <= max_committed_epoch); + self.register_table_ids_for_test(&pairs).await?; + tracing::info!("Registered table ids {:?}", pairs); } - let last_read_epoch = std::cmp::min(snapshot_committed_epoch, max_committed_epoch); - let mut pinned_snapshots = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning_guard.pinned_snapshots,) - ); - let mut context_pinned_snapshot = pinned_snapshots.new_entry_txn_or_default( - context_id, - HummockPinnedSnapshot { - context_id, - minimal_pinned_snapshot: INVALID_EPOCH, - }, - ); + commit_multi_var!(self.meta_store_ref(), compaction_groups_txn)?; - // Unpin the snapshots pinned by meta but frontend doesn't know. Also equal to unpin all - // epochs below specific watermark. - if context_pinned_snapshot.minimal_pinned_snapshot < last_read_epoch - || context_pinned_snapshot.minimal_pinned_snapshot == INVALID_EPOCH - { - context_pinned_snapshot.minimal_pinned_snapshot = last_read_epoch; - commit_multi_var!(self.meta_store_ref(), context_pinned_snapshot)?; - trigger_pin_unpin_snapshot_state(&self.metrics, &versioning_guard.pinned_snapshots); + // Notify that tables have created + for table in table_catalogs { + self.env + .notification_manager() + .notify_hummock_relation_info(Operation::Add, RelationInfo::Table(table.clone())) + .await; + self.env + .notification_manager() + .notify_compactor_relation_info(Operation::Add, RelationInfo::Table(table)) + .await; } - #[cfg(test)] - { - drop(versioning_guard); - self.check_state_consistency().await; + tracing::info!("Inited compaction groups:"); + for group in compaction_groups { + tracing::info!("{:?}", group); } - Ok(()) } - #[named] - pub async fn get_compact_tasks_impl( + /// Replay a version delta to current hummock version. + /// Returns the `version_id`, `max_committed_epoch` of the new version and the modified + /// compaction groups + pub async fn replay_version_delta( &self, - compaction_groups: Vec, - max_select_count: usize, - selector: &mut Box, - ) -> Result<(Vec, Vec)> { - // TODO: `get_all_table_options` will hold catalog_manager async lock, to avoid holding the - // lock in compaction_guard, take out all table_options in advance there may be a - // waste of resources here, need to add a more efficient filter in catalog_manager - let deterministic_mode = self.env.opts.compaction_deterministic_test; - let all_table_id_to_option = self - .metadata_manager - .get_all_table_options() - .await - .map_err(|err| Error::MetaStore(err.into()))?; - - let mut compaction_guard = write_lock!(self, compaction).await; - let compaction = compaction_guard.deref_mut(); - let mut versioning_guard = write_lock!(self, versioning).await; - let versioning = versioning_guard.deref_mut(); - - let _timer = start_measure_real_process_timer!(self); - - let mut current_version = versioning.current_version.clone(); - let start_time = Instant::now(); - let max_committed_epoch = current_version.max_committed_epoch; - let watermark = versioning - .pinned_snapshots - .values() - .map(|v| v.minimal_pinned_snapshot) - .fold(max_committed_epoch, std::cmp::min); - let last_apply_version_id = current_version.id; - - let mut compaction_statuses = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut compaction.compaction_statuses) - ); - - let mut compact_task_assignment = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut compaction.compact_task_assignment) - ); - - let mut hummock_version_deltas = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.hummock_version_deltas) - ); - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); - - let mut unschedule_groups = vec![]; - let mut trivial_tasks = vec![]; - let mut pick_tasks = vec![]; - let developer_config = Arc::new(CompactionDeveloperConfig::new_from_meta_opts( - &self.env.opts, - )); - const MAX_TRIVIAL_MOVE_TASK_COUNT: usize = 256; - 'outside: for compaction_group_id in compaction_groups { - if pick_tasks.len() >= max_select_count { - break; - } - - if current_version.levels.get(&compaction_group_id).is_none() { - continue; - } - - // When the last table of a compaction group is deleted, the compaction group (and its - // config) is destroyed as well. Then a compaction task for this group may come later and - // cannot find its config. - let group_config = match self - .compaction_group_manager - .read() - .await - .try_get_compaction_group_config(compaction_group_id) - { - Some(config) => config, - None => continue, - }; - // StoredIdGenerator already implements ids pre-allocation by ID_PREALLOCATE_INTERVAL. - let task_id = next_compaction_task_id(&self.env).await?; - - if !compaction_statuses.contains_key(&compaction_group_id) { - // lazy initialize. - compaction_statuses.insert( - compaction_group_id, - CompactStatus::new( - compaction_group_id, - group_config.compaction_config.max_level, - ), - ); - } - let mut compact_status = compaction_statuses.get_mut(compaction_group_id).unwrap(); - - let can_trivial_move = matches!(selector.task_type(), TaskType::Dynamic) - || matches!(selector.task_type(), TaskType::Emergency); - - let mut stats = LocalSelectorStatistic::default(); - let member_table_ids = current_version - .get_compaction_group_levels(compaction_group_id) - .member_table_ids - .clone(); - - let mut table_id_to_option: HashMap = HashMap::default(); - - for table_id in &member_table_ids { - if let Some(opts) = all_table_id_to_option.get(table_id) { - table_id_to_option.insert(*table_id, *opts); - } - } - - let table_to_vnode_partition = match self - .group_to_table_vnode_partition - .read() - .get(&compaction_group_id) - { - Some(table_to_vnode_partition) => table_to_vnode_partition.clone(), - None => BTreeMap::default(), - }; - - while let Some(compact_task) = compact_status.get_compact_task( - current_version.get_compaction_group_levels(compaction_group_id), - task_id as HummockCompactionTaskId, - &group_config, - &mut stats, - selector, - table_id_to_option.clone(), - developer_config.clone(), - ) { - let target_level_id = compact_task.input.target_level as u32; - - let compression_algorithm = match compact_task.compression_algorithm.as_str() { - "Lz4" => 1, - "Zstd" => 2, - _ => 0, - }; - let vnode_partition_count = compact_task.input.vnode_partition_count; - use risingwave_hummock_sdk::prost_key_range::KeyRangeExt; - - let mut compact_task = CompactTask { - input_ssts: compact_task.input.input_levels, - splits: vec![risingwave_pb::hummock::KeyRange::inf()], - watermark, - sorted_output_ssts: vec![], - task_id, - target_level: target_level_id, - // only gc delete keys in last level because there may be older version in more bottom - // level. - gc_delete_keys: current_version - .get_compaction_group_levels(compaction_group_id) - .is_last_level(target_level_id), - base_level: compact_task.base_level as u32, - task_status: TaskStatus::Pending as i32, - compaction_group_id: group_config.group_id, - existing_table_ids: member_table_ids.clone(), - compression_algorithm, - target_file_size: compact_task.target_file_size, - table_options: table_id_to_option - .iter() - .map(|(table_id, table_option)| { - (*table_id, TableOption::from(table_option)) - }) - .collect(), - current_epoch_time: Epoch::now().0, - compaction_filter_mask: group_config.compaction_config.compaction_filter_mask, - target_sub_level_id: compact_task.input.target_sub_level_id, - task_type: compact_task.compaction_task_type as i32, - split_weight_by_vnode: vnode_partition_count, - ..Default::default() - }; - - let is_trivial_reclaim = CompactStatus::is_trivial_reclaim(&compact_task); - let is_trivial_move = CompactStatus::is_trivial_move_task(&compact_task); - if is_trivial_reclaim || (is_trivial_move && can_trivial_move) { - let log_label = if is_trivial_reclaim { - "TrivialReclaim" - } else { - "TrivialMove" - }; - let label = if is_trivial_reclaim { - "trivial-space-reclaim" - } else { - "trivial-move" - }; - - tracing::debug!( - "{} for compaction group {}: input: {:?}, cost time: {:?}", - log_label, - compact_task.compaction_group_id, - compact_task.input_ssts, - start_time.elapsed() - ); - compact_task.set_task_status(TaskStatus::Success); - compact_status.report_compact_task(&compact_task); - if !is_trivial_reclaim { - compact_task.sorted_output_ssts = - compact_task.input_ssts[0].table_infos.clone(); - } - self.metrics - .compact_frequency - .with_label_values(&[ - label, - &compact_task.compaction_group_id.to_string(), - selector.task_type().as_str_name(), - "SUCCESS", - ]) - .inc(); - let version_delta = gen_version_delta( - &mut hummock_version_deltas, - &mut branched_ssts, - ¤t_version, - &compact_task, - deterministic_mode, - ); - current_version.apply_version_delta(&version_delta); - trivial_tasks.push(compact_task); - if trivial_tasks.len() >= MAX_TRIVIAL_MOVE_TASK_COUNT { - break 'outside; - } - } else { - if group_config.compaction_config.split_weight_by_vnode > 0 { - for table_id in &compact_task.existing_table_ids { - compact_task - .table_vnode_partition - .insert(*table_id, vnode_partition_count); - } - } else { - compact_task.table_vnode_partition = table_to_vnode_partition.clone(); - } - compact_task - .table_vnode_partition - .retain(|table_id, _| compact_task.existing_table_ids.contains(table_id)); - compact_task.table_watermarks = current_version - .safe_epoch_table_watermarks(&compact_task.existing_table_ids); - - if self.env.opts.enable_dropped_column_reclaim { - // TODO: get all table schemas for all tables in once call to avoid acquiring lock and await. - compact_task.table_schemas = match self.metadata_manager() { - MetadataManager::V1(mgr) => mgr - .catalog_manager - .get_versioned_table_schemas(&compact_task.existing_table_ids) - .await - .into_iter() - .map(|(table_id, column_ids)| { - (table_id, TableSchema { column_ids }) - }) - .collect(), - MetadataManager::V2(_) => { - // TODO #13952: support V2 - BTreeMap::default() - } - }; - } - - compact_task_assignment.insert( - compact_task.task_id, - CompactTaskAssignment { - compact_task: Some(compact_task.clone()), - context_id: META_NODE_ID, // deprecated - }, - ); - - pick_tasks.push(compact_task); - break; - } - - stats.report_to_metrics(compaction_group_id, self.metrics.as_ref()); - stats = LocalSelectorStatistic::default(); - } - if pick_tasks - .last() - .map(|task| task.compaction_group_id != compaction_group_id) - .unwrap_or(true) - { - unschedule_groups.push(compaction_group_id); - } - - stats.report_to_metrics(compaction_group_id, self.metrics.as_ref()); - } - - if !trivial_tasks.is_empty() { - commit_multi_var!( - self.meta_store_ref(), - compaction_statuses, - compact_task_assignment, - hummock_version_deltas - )?; - branched_ssts.commit_memory(); - - trigger_version_stat(&self.metrics, ¤t_version); - versioning.current_version = current_version; - trigger_delta_log_stats(&self.metrics, versioning.hummock_version_deltas.len()); - self.notify_stats(&versioning.version_stats); - self.notify_version_deltas(versioning, last_apply_version_id); - self.metrics - .compact_task_batch_count - .with_label_values(&["batch_trivial_move"]) - .observe(trivial_tasks.len() as f64); - drop(versioning_guard); - } else { - // We are using a single transaction to ensure that each task has progress when it is - // created. - drop(versioning_guard); - commit_multi_var!( - self.meta_store_ref(), - compaction_statuses, - compact_task_assignment - )?; - } - drop(compaction_guard); - if !pick_tasks.is_empty() { - self.metrics - .compact_task_batch_count - .with_label_values(&["batch_get_compact_task"]) - .observe(pick_tasks.len() as f64); - } - - for compact_task in &mut pick_tasks { - let compaction_group_id = compact_task.compaction_group_id; - - // Initiate heartbeat for the task to track its progress. - self.compactor_manager - .initiate_task_heartbeat(compact_task.clone()); - - // this task has been finished. - compact_task.set_task_status(TaskStatus::Pending); - let compact_task_statistics = statistics_compact_task(compact_task); - - let level_type_label = build_compact_task_level_type_metrics_label( - compact_task.input_ssts[0].level_idx as usize, - compact_task.input_ssts.last().unwrap().level_idx as usize, - ); - - let level_count = compact_task.input_ssts.len(); - if compact_task.input_ssts[0].level_idx == 0 { - self.metrics - .l0_compact_level_count - .with_label_values(&[&compaction_group_id.to_string(), &level_type_label]) - .observe(level_count as _); - } - - self.metrics - .compact_task_size - .with_label_values(&[&compaction_group_id.to_string(), &level_type_label]) - .observe(compact_task_statistics.total_file_size as _); - - self.metrics - .compact_task_size - .with_label_values(&[ - &compaction_group_id.to_string(), - &format!("{} uncompressed", level_type_label), - ]) - .observe(compact_task_statistics.total_uncompressed_file_size as _); - - self.metrics - .compact_task_file_count - .with_label_values(&[&compaction_group_id.to_string(), &level_type_label]) - .observe(compact_task_statistics.total_file_count as _); - - tracing::trace!( - "For compaction group {}: pick up {} {} sub_level in level {} to compact to target {}. cost time: {:?} compact_task_statistics {:?}", - compaction_group_id, - level_count, - compact_task.input_ssts[0].level_type().as_str_name(), - compact_task.input_ssts[0].level_idx, - compact_task.target_level, - start_time.elapsed(), - compact_task_statistics - ); - } + mut version_delta: HummockVersionDelta, + ) -> Result<(HummockVersion, Vec)> { + let mut versioning_guard = self.versioning.write().await; + // ensure the version id is ascending after replay + version_delta.id = versioning_guard.current_version.next_version_id(); + version_delta.prev_id = versioning_guard.current_version.id; + versioning_guard + .current_version + .apply_version_delta(&version_delta); - #[cfg(test)] - { - self.check_state_consistency().await; - } - pick_tasks.extend(trivial_tasks); - Ok((pick_tasks, unschedule_groups)) + let version_new = versioning_guard.current_version.clone(); + let compaction_group_ids = version_delta.group_deltas.keys().cloned().collect_vec(); + Ok((version_new, compaction_group_ids)) } - /// Cancels a compaction task no matter it's assigned or unassigned. - pub async fn cancel_compact_task(&self, task_id: u64, task_status: TaskStatus) -> Result { - fail_point!("fp_cancel_compact_task", |_| Err(Error::MetaStore( - anyhow::anyhow!("failpoint metastore err") - ))); - let ret = self - .cancel_compact_task_impl(vec![task_id], task_status) - .await?; - Ok(ret[0]) + pub async fn disable_commit_epoch(&self) -> HummockVersion { + let mut versioning_guard = self.versioning.write().await; + versioning_guard.disable_commit_epochs = true; + versioning_guard.current_version.clone() } - pub async fn cancel_compact_tasks( - &self, - tasks: Vec, - task_status: TaskStatus, - ) -> Result> { - self.cancel_compact_task_impl(tasks, task_status).await + pub fn metadata_manager(&self) -> &MetadataManager { + &self.metadata_manager } - pub async fn cancel_compact_task_impl( - &self, - task_ids: Vec, - task_status: TaskStatus, - ) -> Result> { - assert!(CANCEL_STATUS_SET.contains(&task_status)); - let tasks = task_ids - .into_iter() - .map(|task_id| ReportTask { - task_id, - task_status: task_status as i32, - sorted_output_ssts: vec![], - table_stats_change: HashMap::default(), - }) - .collect_vec(); - let rets = self.report_compact_tasks(tasks).await?; - #[cfg(test)] - { - self.check_state_consistency().await; - } - Ok(rets) + pub fn object_store_media_type(&self) -> &'static str { + self.object_store.media_type() } +} - pub async fn get_compact_tasks( - &self, - mut compaction_groups: Vec, - max_select_count: usize, - selector: &mut Box, - ) -> Result<(Vec, Vec)> { - fail_point!("fp_get_compact_task", |_| Err(Error::MetaStore( - anyhow::anyhow!("failpoint metastore error") - ))); - compaction_groups.shuffle(&mut thread_rng()); - let (mut tasks, groups) = self - .get_compact_tasks_impl(compaction_groups, max_select_count, selector) - .await?; - tasks.retain(|task| { - if task.task_status() == TaskStatus::Success { - debug_assert!( - CompactStatus::is_trivial_reclaim(task) - || CompactStatus::is_trivial_move_task(task) - ); - false - } else { - true +async fn write_exclusive_cluster_id( + state_store_dir: &str, + cluster_id: ClusterId, + object_store: ObjectStoreRef, +) -> Result<()> { + const CLUSTER_ID_DIR: &str = "cluster_id"; + const CLUSTER_ID_NAME: &str = "0"; + let cluster_id_dir = format!("{}/{}/", state_store_dir, CLUSTER_ID_DIR); + let cluster_id_full_path = format!("{}{}", cluster_id_dir, CLUSTER_ID_NAME); + match object_store.read(&cluster_id_full_path, ..).await { + Ok(stored_cluster_id) => { + let stored_cluster_id = String::from_utf8(stored_cluster_id.to_vec()).unwrap(); + if cluster_id.deref() == stored_cluster_id { + return Ok(()); } - }); - Ok((tasks, groups)) - } - - pub async fn get_compact_task( - &self, - compaction_group_id: CompactionGroupId, - selector: &mut Box, - ) -> Result> { - fail_point!("fp_get_compact_task", |_| Err(Error::MetaStore( - anyhow::anyhow!("failpoint metastore error") - ))); - let (normal_tasks, _) = self - .get_compact_tasks_impl(vec![compaction_group_id], 1, selector) - .await?; - for task in normal_tasks { - if task.task_status() != TaskStatus::Success { - return Ok(Some(task)); - } - debug_assert!( - CompactStatus::is_trivial_reclaim(&task) - || CompactStatus::is_trivial_move_task(&task) - ); + Err(ObjectError::internal(format!( + "Data directory is already used by another cluster with id {:?}, path {}.", + stored_cluster_id, cluster_id_full_path, + )) + .into()) } - Ok(None) - } - - pub async fn manual_get_compact_task( - &self, - compaction_group_id: CompactionGroupId, - manual_compaction_option: ManualCompactionOption, - ) -> Result> { - let mut selector: Box = - Box::new(ManualCompactionSelector::new(manual_compaction_option)); - self.get_compact_task(compaction_group_id, &mut selector) - .await - } - - fn is_compact_task_expired( - compact_task: &CompactTask, - branched_ssts: &BTreeMap, - ) -> bool { - for input_level in compact_task.get_input_ssts() { - for table_info in input_level.get_table_infos() { - if let Some(mp) = branched_ssts.get(&table_info.object_id) { - if mp - .get(&compact_task.compaction_group_id) - .map_or(true, |sst_id| *sst_id != table_info.sst_id) - { - return true; - } - } - } - } - false - } - - pub async fn report_compact_task( - &self, - task_id: u64, - task_status: TaskStatus, - sorted_output_ssts: Vec, - table_stats_change: Option, - ) -> Result { - let rets = self - .report_compact_tasks(vec![ReportTask { - task_id, - task_status: task_status as i32, - sorted_output_ssts, - table_stats_change: table_stats_change.unwrap_or_default(), - }]) - .await?; - Ok(rets[0]) - } - - /// Finishes or cancels a compaction task, according to `task_status`. - /// - /// If `context_id` is not None, its validity will be checked when writing meta store. - /// Its ownership of the task is checked as well. - /// - /// Return Ok(false) indicates either the task is not found, - /// or the task is not owned by `context_id` when `context_id` is not None. - #[named] - pub async fn report_compact_tasks(&self, report_tasks: Vec) -> Result> { - let mut guard = write_lock!(self, compaction).await; - let deterministic_mode = self.env.opts.compaction_deterministic_test; - let compaction = guard.deref_mut(); - let start_time = Instant::now(); - let original_keys = compaction.compaction_statuses.keys().cloned().collect_vec(); - let mut compact_statuses = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut compaction.compaction_statuses,) - ); - let mut rets = vec![false; report_tasks.len()]; - let mut compact_task_assignment = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut compaction.compact_task_assignment,) - ); - // The compaction task is finished. - let mut versioning_guard = write_lock!(self, versioning).await; - let versioning = versioning_guard.deref_mut(); - let _timer = start_measure_real_process_timer!(self); - - let mut current_version = versioning.current_version.clone(); - // purge stale compact_status - for group_id in original_keys { - if !current_version.levels.contains_key(&group_id) { - compact_statuses.remove(group_id); - } - } - let mut tasks = vec![]; - - let mut hummock_version_deltas = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.hummock_version_deltas,) - ); - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); - - let mut version_stats = create_trx_wrapper!( - self.meta_store_ref(), - VarTransactionWrapper, - VarTransaction::new(&mut versioning.version_stats) - ); - let mut success_count = 0; - let last_version_id = current_version.id; - for (idx, task) in report_tasks.into_iter().enumerate() { - rets[idx] = true; - let mut compact_task = match compact_task_assignment.remove(task.task_id) { - Some(compact_task) => compact_task.compact_task.unwrap(), - None => { - tracing::warn!("{}", format!("compact task {} not found", task.task_id)); - rets[idx] = false; - continue; - } - }; - - { - // apply result - compact_task.task_status = task.task_status; - compact_task.sorted_output_ssts = task.sorted_output_ssts; - } - - match compact_statuses.get_mut(compact_task.compaction_group_id) { - Some(mut compact_status) => { - compact_status.report_compact_task(&compact_task); - } - None => { - compact_task.set_task_status(TaskStatus::InvalidGroupCanceled); - } - } - - let input_sst_ids: HashSet = compact_task - .input_ssts - .iter() - .flat_map(|level| level.table_infos.iter().map(|sst| sst.sst_id)) - .collect(); - let input_level_ids: Vec = compact_task - .input_ssts - .iter() - .map(|level| level.level_idx) - .collect(); - let is_success = if let TaskStatus::Success = compact_task.task_status() { - // if member_table_ids changes, the data of sstable may stale. - let is_expired = - Self::is_compact_task_expired(&compact_task, branched_ssts.tree_ref()); - if is_expired { - compact_task.set_task_status(TaskStatus::InputOutdatedCanceled); - false - } else { - let group = current_version - .levels - .get(&compact_task.compaction_group_id) - .unwrap(); - let input_exist = - group.check_deleted_sst_exist(&input_level_ids, input_sst_ids); - if !input_exist { - compact_task.set_task_status(TaskStatus::InvalidGroupCanceled); - warn!( - "The task may be expired because of group split, task:\n {:?}", - compact_task_to_string(&compact_task) - ); - } - input_exist - } - } else { - false - }; - if is_success { - success_count += 1; - let version_delta = gen_version_delta( - &mut hummock_version_deltas, - &mut branched_ssts, - ¤t_version, - &compact_task, - deterministic_mode, - ); - // apply version delta before we persist this change. If it causes panic we can - // recover to a correct state after restarting meta-node. - current_version.apply_version_delta(&version_delta); - if purge_prost_table_stats(&mut version_stats.table_stats, ¤t_version) { - self.metrics.version_stats.reset(); - versioning.local_metrics.clear(); - } - add_prost_table_stats_map(&mut version_stats.table_stats, &task.table_stats_change); - trigger_local_table_stat( - &self.metrics, - &mut versioning.local_metrics, - &version_stats, - &task.table_stats_change, - ); - } - tasks.push(compact_task); - } - if success_count > 0 { - commit_multi_var!( - self.meta_store_ref(), - compact_statuses, - compact_task_assignment, - hummock_version_deltas, - version_stats - )?; - branched_ssts.commit_memory(); - - trigger_version_stat(&self.metrics, ¤t_version); - trigger_delta_log_stats(&self.metrics, versioning.hummock_version_deltas.len()); - self.notify_stats(&versioning.version_stats); - versioning.current_version = current_version; - - if !deterministic_mode { - self.notify_version_deltas(versioning, last_version_id); - } - - self.metrics - .compact_task_batch_count - .with_label_values(&["batch_report_task"]) - .observe(success_count as f64); - } else { - // The compaction task is cancelled or failed. - commit_multi_var!( - self.meta_store_ref(), - compact_statuses, - compact_task_assignment - )?; - } - let mut success_groups = vec![]; - for compact_task in tasks { - let task_status = compact_task.task_status(); - let task_status_label = task_status.as_str_name(); - let task_type_label = compact_task.task_type().as_str_name(); - - self.compactor_manager - .remove_task_heartbeat(compact_task.task_id); - - self.metrics - .compact_frequency - .with_label_values(&[ - "normal", - &compact_task.compaction_group_id.to_string(), - task_type_label, - task_status_label, - ]) - .inc(); - - tracing::trace!( - "Reported compaction task. {}. cost time: {:?}", - compact_task_to_string(&compact_task), - start_time.elapsed(), - ); - - trigger_sst_stat( - &self.metrics, - compaction - .compaction_statuses - .get(&compact_task.compaction_group_id), - &versioning_guard.current_version, - compact_task.compaction_group_id, - ); - - if !deterministic_mode - && (matches!(compact_task.task_type(), compact_task::TaskType::Dynamic) - || matches!(compact_task.task_type(), compact_task::TaskType::Emergency)) - { - // only try send Dynamic compaction - self.try_send_compaction_request( - compact_task.compaction_group_id, - compact_task::TaskType::Dynamic, - ); - } - - if task_status == TaskStatus::Success { - success_groups.push(compact_task.compaction_group_id); - } - } - drop(versioning_guard); - if !success_groups.is_empty() { - self.try_update_write_limits(&success_groups).await; - } - Ok(rets) - } - - /// Caller should ensure `epoch` > `max_committed_epoch` - #[named] - pub async fn commit_epoch( - &self, - epoch: HummockEpoch, - commit_info: CommitEpochInfo, - ) -> Result> { - let CommitEpochInfo { - mut sstables, - new_table_watermarks, - sst_to_context, - new_table_fragment_info, - } = commit_info; - let mut versioning_guard = write_lock!(self, versioning).await; - let _timer = start_measure_real_process_timer!(self); - // Prevent commit new epochs if this flag is set - if versioning_guard.disable_commit_epochs { - return Ok(None); - } - - let versioning = versioning_guard.deref_mut(); - self.commit_epoch_sanity_check( - epoch, - &sstables, - &sst_to_context, - &versioning.current_version, - ) - .await?; - - // Consume and aggregate table stats. - let mut table_stats_change = PbTableStatsMap::default(); - for s in &mut sstables { - add_prost_table_stats_map(&mut table_stats_change, &std::mem::take(&mut s.table_stats)); - } - - let old_version: &HummockVersion = &versioning.current_version; - let mut new_version_delta = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapEntryTransactionWrapper, - BTreeMapEntryTransaction::new_insert( - &mut versioning.hummock_version_deltas, - old_version.id + 1, - build_version_delta_after_version(old_version), - ) - ); - new_version_delta.max_committed_epoch = epoch; - new_version_delta.new_table_watermarks = new_table_watermarks; - - let mut table_compaction_group_mapping = old_version.build_compaction_group_info(); - - // Add new table - if let Some(new_fragment_table_info) = new_table_fragment_info { - if !new_fragment_table_info.internal_table_ids.is_empty() { - if let Some(levels) = old_version - .levels - .get(&(StaticCompactionGroupId::StateDefault as u64)) - { - for table_id in &new_fragment_table_info.internal_table_ids { - if levels.member_table_ids.contains(&table_id.table_id) { - return Err(Error::CompactionGroup(format!( - "table {} already in group {}", - table_id, - StaticCompactionGroupId::StateDefault as u64 - ))); - } - } - } - - let group_deltas = &mut new_version_delta - .group_deltas - .entry(StaticCompactionGroupId::StateDefault as u64) - .or_default() - .group_deltas; - group_deltas.push(GroupDelta { - delta_type: Some(DeltaType::GroupMetaChange(GroupMetaChange { - table_ids_add: new_fragment_table_info - .internal_table_ids - .iter() - .map(|table_id| table_id.table_id) - .collect(), - ..Default::default() - })), - }); - - for table_id in &new_fragment_table_info.internal_table_ids { - table_compaction_group_mapping - .insert(*table_id, StaticCompactionGroupId::StateDefault as u64); - } - } - - if let Some(table_id) = new_fragment_table_info.mv_table_id { - if let Some(levels) = old_version - .levels - .get(&(StaticCompactionGroupId::MaterializedView as u64)) - { - if levels.member_table_ids.contains(&table_id.table_id) { - return Err(Error::CompactionGroup(format!( - "table {} already in group {}", - table_id, - StaticCompactionGroupId::MaterializedView as u64 - ))); - } - } - let group_deltas = &mut new_version_delta - .group_deltas - .entry(StaticCompactionGroupId::MaterializedView as u64) - .or_default() - .group_deltas; - group_deltas.push(GroupDelta { - delta_type: Some(DeltaType::GroupMetaChange(GroupMetaChange { - table_ids_add: vec![table_id.table_id], - ..Default::default() - })), - }); - let _ = table_compaction_group_mapping - .insert(table_id, StaticCompactionGroupId::MaterializedView as u64); - } - } - - let mut incorrect_ssts = vec![]; - let mut new_sst_id_number = 0; - for ExtendedSstableInfo { - compaction_group_id, - sst_info: sst, - .. - } in &mut sstables - { - let is_sst_belong_to_group_declared = match old_version.levels.get(compaction_group_id) - { - Some(compaction_group) => sst - .table_ids - .iter() - .all(|t| compaction_group.member_table_ids.contains(t)), - None => false, - }; - if !is_sst_belong_to_group_declared { - let mut group_table_ids: BTreeMap<_, Vec> = BTreeMap::new(); - for table_id in sst.get_table_ids() { - match table_compaction_group_mapping.get(&TableId::new(*table_id)) { - Some(compaction_group_id) => { - group_table_ids - .entry(*compaction_group_id) - .or_default() - .push(*table_id); - } - None => { - tracing::warn!( - "table {} in SST {} doesn't belong to any compaction group", - table_id, - sst.get_object_id(), - ); - } - } - } - let is_trivial_adjust = group_table_ids.len() == 1 - && group_table_ids.first_key_value().unwrap().1.len() - == sst.get_table_ids().len(); - if is_trivial_adjust { - *compaction_group_id = *group_table_ids.first_key_value().unwrap().0; - // is_sst_belong_to_group_declared = true; - } else { - new_sst_id_number += group_table_ids.len(); - incorrect_ssts.push((std::mem::take(sst), group_table_ids)); - *compaction_group_id = - StaticCompactionGroupId::NewCompactionGroup as CompactionGroupId; - } - } - } - let mut new_sst_id = next_sstable_object_id(&self.env, new_sst_id_number).await?; - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); - let original_sstables = std::mem::take(&mut sstables); - sstables.reserve_exact(original_sstables.len() - incorrect_ssts.len() + new_sst_id_number); - let mut incorrect_ssts = incorrect_ssts.into_iter(); - for original_sstable in original_sstables { - if original_sstable.compaction_group_id - == StaticCompactionGroupId::NewCompactionGroup as CompactionGroupId - { - let (sst, group_table_ids) = incorrect_ssts.next().unwrap(); - let mut branch_groups = HashMap::new(); - for (group_id, _match_ids) in group_table_ids { - let mut branch_sst = sst.clone(); - branch_sst.sst_id = new_sst_id; - sstables.push(ExtendedSstableInfo::with_compaction_group( - group_id, branch_sst, - )); - branch_groups.insert(group_id, new_sst_id); - new_sst_id += 1; - } - if !branch_groups.is_empty() { - branched_ssts.insert(sst.get_object_id(), branch_groups); - } - } else { - sstables.push(original_sstable); - } - } - - let mut modified_compaction_groups = vec![]; - // Append SSTs to a new version. - for (compaction_group_id, sstables) in &sstables - .into_iter() - // the sort is stable sort, and will not change the order within compaction group. - // Do a sort so that sst in the same compaction group can be consecutive - .sorted_by_key( - |ExtendedSstableInfo { - compaction_group_id, - .. - }| *compaction_group_id, - ) - .group_by( - |ExtendedSstableInfo { - compaction_group_id, - .. - }| *compaction_group_id, - ) - { - modified_compaction_groups.push(compaction_group_id); - let group_sstables = sstables - .into_iter() - .map(|ExtendedSstableInfo { sst_info, .. }| sst_info) - .collect_vec(); - - let group_deltas = &mut new_version_delta - .group_deltas - .entry(compaction_group_id) - .or_default() - .group_deltas; - let l0_sub_level_id = epoch; - let group_delta = GroupDelta { - delta_type: Some(DeltaType::IntraLevel(IntraLevelDelta { - level_idx: 0, - inserted_table_infos: group_sstables.clone(), - l0_sub_level_id, - ..Default::default() - })), - }; - group_deltas.push(group_delta); - } - - let mut new_hummock_version = old_version.clone(); - // Create a new_version, possibly merely to bump up the version id and max_committed_epoch. - new_hummock_version.apply_version_delta(new_version_delta.deref()); - - // Apply stats changes. - let mut version_stats = create_trx_wrapper!( - self.meta_store_ref(), - VarTransactionWrapper, - VarTransaction::new(&mut versioning.version_stats) - ); - add_prost_table_stats_map(&mut version_stats.table_stats, &table_stats_change); - if purge_prost_table_stats(&mut version_stats.table_stats, &new_hummock_version) { - self.metrics.version_stats.reset(); - versioning.local_metrics.clear(); - } - - trigger_local_table_stat( - &self.metrics, - &mut versioning.local_metrics, - &version_stats, - &table_stats_change, - ); - for (table_id, stats) in &table_stats_change { - if stats.total_key_size == 0 - && stats.total_value_size == 0 - && stats.total_key_count == 0 - { - continue; - } - let stats_value = std::cmp::max(0, stats.total_key_size + stats.total_value_size); - let table_metrics = get_or_create_local_table_stat( - &self.metrics, - *table_id, - &mut versioning.local_metrics, - ); - table_metrics.inc_write_throughput(stats_value as u64); - } - commit_multi_var!(self.meta_store_ref(), new_version_delta, version_stats)?; - branched_ssts.commit_memory(); - versioning.current_version = new_hummock_version; - - let snapshot = HummockSnapshot { - committed_epoch: epoch, - current_epoch: epoch, - }; - let prev_snapshot = self.latest_snapshot.swap(snapshot.clone().into()); - assert!(prev_snapshot.committed_epoch < epoch); - assert!(prev_snapshot.current_epoch < epoch); - - trigger_version_stat(&self.metrics, &versioning.current_version); - for compaction_group_id in &modified_compaction_groups { - trigger_sst_stat( - &self.metrics, - None, - &versioning.current_version, - *compaction_group_id, - ); - } - - tracing::trace!("new committed epoch {}", epoch); - - self.notify_last_version_delta(versioning); - trigger_delta_log_stats(&self.metrics, versioning.hummock_version_deltas.len()); - self.notify_stats(&versioning.version_stats); - let mut table_groups = HashMap::::default(); - for group in versioning.current_version.levels.values() { - for table_id in &group.member_table_ids { - table_groups.insert(*table_id, group.member_table_ids.len()); - } - } - drop(versioning_guard); - // Don't trigger compactions if we enable deterministic compaction - if !self.env.opts.compaction_deterministic_test { - // commit_epoch may contains SSTs from any compaction group - for id in &modified_compaction_groups { - self.try_send_compaction_request(*id, compact_task::TaskType::Dynamic); - } - if !table_stats_change.is_empty() { - table_stats_change.retain(|table_id, _| { - table_groups - .get(table_id) - .map(|table_count| *table_count > 1) - .unwrap_or(false) - }); - } - if !table_stats_change.is_empty() { - self.collect_table_write_throughput(table_stats_change); - } - } - if !modified_compaction_groups.is_empty() { - self.try_update_write_limits(&modified_compaction_groups) - .await; - } - #[cfg(test)] - { - self.check_state_consistency().await; - } - Ok(Some(snapshot)) - } - - /// We don't commit an epoch without checkpoint. We will only update the `max_current_epoch`. - pub fn update_current_epoch(&self, max_current_epoch: HummockEpoch) -> HummockSnapshot { - // We only update `max_current_epoch`! - let prev_snapshot = self.latest_snapshot.rcu(|snapshot| HummockSnapshot { - committed_epoch: snapshot.committed_epoch, - current_epoch: max_current_epoch, - }); - assert!(prev_snapshot.current_epoch < max_current_epoch); - - tracing::trace!("new current epoch {}", max_current_epoch); - HummockSnapshot { - committed_epoch: prev_snapshot.committed_epoch, - current_epoch: max_current_epoch, - } - } - - pub async fn get_new_sst_ids(&self, number: u32) -> Result { - let start_id = next_sstable_object_id(&self.env, number).await?; - Ok(SstObjectIdRange::new(start_id, start_id + number as u64)) - } - - #[named] - pub async fn get_min_pinned_version_id(&self) -> HummockVersionId { - read_lock!(self, versioning).await.min_pinned_version_id() - } - - #[named] - #[cfg(test)] - pub async fn check_state_consistency(&self) { - let mut compaction_guard = write_lock!(self, compaction).await; - let mut versioning_guard = write_lock!(self, versioning).await; - let objects_to_delete = versioning_guard.objects_to_delete.clone(); - // We don't check `checkpoint` because it's allowed to update its in memory state without - // persisting to object store. - let get_state = - |compaction_guard: &RwLockWriteGuard<'_, Compaction>, - versioning_guard: &RwLockWriteGuard<'_, Versioning>| { - let compact_statuses_copy = compaction_guard.compaction_statuses.clone(); - let compact_task_assignment_copy = compaction_guard.compact_task_assignment.clone(); - let pinned_versions_copy = versioning_guard.pinned_versions.clone(); - let pinned_snapshots_copy = versioning_guard.pinned_snapshots.clone(); - let hummock_version_deltas_copy = versioning_guard.hummock_version_deltas.clone(); - let version_stats_copy = versioning_guard.version_stats.clone(); - let branched_ssts = versioning_guard.branched_ssts.clone(); - ( - ( - compact_statuses_copy, - compact_task_assignment_copy, - pinned_versions_copy, - pinned_snapshots_copy, - hummock_version_deltas_copy, - version_stats_copy, - ), - branched_ssts, - ) - }; - let (mem_state, branched_ssts) = get_state(&compaction_guard, &versioning_guard); - self.load_meta_store_state_impl( - compaction_guard.borrow_mut(), - versioning_guard.borrow_mut(), - ) - .await - .expect("Failed to load state from meta store"); - let (loaded_state, load_branched_ssts) = get_state(&compaction_guard, &versioning_guard); - assert_eq!(branched_ssts, load_branched_ssts); - assert_eq!( - mem_state, loaded_state, - "hummock in-mem state is inconsistent with meta store state", - ); - versioning_guard.objects_to_delete = objects_to_delete; - } - - /// Gets current version without pinning it. - /// Should not be called inside [`HummockManager`], because it requests locks internally. - /// - /// Note: this method can hurt performance because it will clone a large object. - #[named] - pub async fn get_current_version(&self) -> HummockVersion { - read_lock!(self, versioning).await.current_version.clone() - } - - #[named] - pub async fn get_current_max_committed_epoch(&self) -> HummockEpoch { - read_lock!(self, versioning) - .await - .current_version - .max_committed_epoch - } - - /// Gets branched sstable infos - /// Should not be called inside [`HummockManager`], because it requests locks internally. - #[named] - pub async fn get_branched_ssts_info(&self) -> BTreeMap { - read_lock!(self, versioning).await.branched_ssts.clone() - } - - #[named] - /// Gets the mapping from table id to compaction group id - pub async fn get_table_compaction_group_id_mapping( - &self, - ) -> HashMap { - get_table_compaction_group_id_mapping(&read_lock!(self, versioning).await.current_version) - } - - /// Get version deltas from meta store - #[cfg_attr(coverage, coverage(off))] - #[named] - pub async fn list_version_deltas( - &self, - start_id: u64, - num_limit: u32, - committed_epoch_limit: HummockEpoch, - ) -> Result> { - let versioning = read_lock!(self, versioning).await; - let version_deltas = versioning - .hummock_version_deltas - .range(start_id..) - .map(|(_id, delta)| delta) - .filter(|delta| delta.max_committed_epoch <= committed_epoch_limit) - .take(num_limit as _) - .cloned() - .collect(); - Ok(version_deltas) - } - - pub async fn init_metadata_for_version_replay( - &self, - table_catalogs: Vec
, - compaction_groups: Vec, - ) -> Result<()> { - for table in &table_catalogs { - table.insert(self.env.meta_store().as_kv()).await?; - } - for group in &compaction_groups { - assert!( - group.id == StaticCompactionGroupId::NewCompactionGroup as u64 - || (group.id >= StaticCompactionGroupId::StateDefault as u64 - && group.id <= StaticCompactionGroupId::MaterializedView as u64), - "compaction group id should be either NewCompactionGroup to create new one, or predefined static ones." - ); - } - - for group in &compaction_groups { - let mut pairs = vec![]; - for table_id in group.member_table_ids.clone() { - pairs.push((table_id as StateTableId, group.id)); - } - let group_config = group.compaction_config.clone().unwrap(); - self.compaction_group_manager - .write() - .await - .init_compaction_config_for_replay(group.id, group_config) - .await - .unwrap(); - self.register_table_ids(&pairs).await?; - tracing::info!("Registered table ids {:?}", pairs); - } - - // Notify that tables have created - for table in table_catalogs { - self.env - .notification_manager() - .notify_hummock_relation_info(Operation::Add, RelationInfo::Table(table.clone())) - .await; - self.env - .notification_manager() - .notify_compactor_relation_info(Operation::Add, RelationInfo::Table(table)) - .await; - } - - tracing::info!("Inited compaction groups:"); - for group in compaction_groups { - tracing::info!("{:?}", group); - } - Ok(()) - } - - /// Replay a version delta to current hummock version. - /// Returns the `version_id`, `max_committed_epoch` of the new version and the modified - /// compaction groups - #[named] - pub async fn replay_version_delta( - &self, - mut version_delta: HummockVersionDelta, - ) -> Result<(HummockVersion, Vec)> { - let mut versioning_guard = write_lock!(self, versioning).await; - // ensure the version id is ascending after replay - version_delta.id = versioning_guard.current_version.id + 1; - version_delta.prev_id = version_delta.id - 1; - versioning_guard - .current_version - .apply_version_delta(&version_delta); - - let version_new = versioning_guard.current_version.clone(); - let compaction_group_ids = version_delta.group_deltas.keys().cloned().collect_vec(); - Ok((version_new, compaction_group_ids)) - } - - #[named] - pub async fn disable_commit_epoch(&self) -> HummockVersion { - let mut versioning_guard = write_lock!(self, versioning).await; - versioning_guard.disable_commit_epochs = true; - versioning_guard.current_version.clone() - } - - /// Triggers compacitons to specified compaction groups. - /// Don't wait for compaction finish - pub async fn trigger_compaction_deterministic( - &self, - _base_version_id: HummockVersionId, - compaction_groups: Vec, - ) -> Result<()> { - let old_version = self.get_current_version().await; - tracing::info!( - "Trigger compaction for version {}, epoch {}, groups {:?}", - old_version.id, - old_version.max_committed_epoch, - compaction_groups - ); - - if compaction_groups.is_empty() { - return Ok(()); - } - for compaction_group in compaction_groups { - self.try_send_compaction_request(compaction_group, compact_task::TaskType::Dynamic); - } - Ok(()) - } - - /// Sends a compaction request. - pub fn try_send_compaction_request( - &self, - compaction_group: CompactionGroupId, - task_type: compact_task::TaskType, - ) -> bool { - match self - .compaction_state - .try_sched_compaction(compaction_group, task_type) - { - Ok(_) => true, - Err(e) => { - tracing::error!( - error = %e.as_report(), - "failed to send compaction request for compaction group {}", - compaction_group, - ); - false - } - } - } - - pub async fn trigger_manual_compaction( - &self, - compaction_group: CompactionGroupId, - manual_compaction_option: ManualCompactionOption, - ) -> Result<()> { - let start_time = Instant::now(); - - // 1. Get idle compactor. - let compactor = match self.compactor_manager.next_compactor() { - Some(compactor) => compactor, - None => { - tracing::warn!("trigger_manual_compaction No compactor is available."); - return Err(anyhow::anyhow!( - "trigger_manual_compaction No compactor is available. compaction_group {}", - compaction_group - ) - .into()); - } - }; - - // 2. Get manual compaction task. - let compact_task = self - .manual_get_compact_task(compaction_group, manual_compaction_option) - .await; - let compact_task = match compact_task { - Ok(Some(compact_task)) => compact_task, - Ok(None) => { - // No compaction task available. - return Err(anyhow::anyhow!( - "trigger_manual_compaction No compaction_task is available. compaction_group {}", - compaction_group - ) - .into()); - } - Err(err) => { - tracing::warn!(error = %err.as_report(), "Failed to get compaction task"); - - return Err(anyhow::anyhow!(err) - .context(format!( - "Failed to get compaction task for compaction_group {}", - compaction_group, - )) - .into()); - } - }; - - // 3. send task to compactor - let compact_task_string = compact_task_to_string(&compact_task); - // TODO: shall we need to cancel on meta ? - compactor - .send_event(ResponseEvent::CompactTask(compact_task)) - .with_context(|| { - format!( - "Failed to trigger compaction task for compaction_group {}", - compaction_group, - ) - })?; - - tracing::info!( - "Trigger manual compaction task. {}. cost time: {:?}", - &compact_task_string, - start_time.elapsed(), - ); - - Ok(()) - } - - #[cfg(any(test, feature = "test"))] - pub fn compactor_manager_ref_for_test(&self) -> CompactorManagerRef { - self.compactor_manager.clone() - } - - #[cfg(any(test, feature = "test"))] - #[named] - pub async fn compaction_task_from_assignment_for_test( - &self, - task_id: u64, - ) -> Option { - let compaction_guard = read_lock!(self, compaction).await; - let assignment_ref = &compaction_guard.compact_task_assignment; - assignment_ref.get(&task_id).cloned() - } - - #[cfg(any(test, feature = "test"))] - #[named] - pub async fn report_compact_task_for_test( - &self, - task_id: u64, - compact_task: Option, - task_status: TaskStatus, - sorted_output_ssts: Vec, - table_stats_change: Option, - ) -> Result<()> { - if let Some(task) = compact_task { - let mut guard = write_lock!(self, compaction).await; - guard.compact_task_assignment.insert( - task_id, - CompactTaskAssignment { - compact_task: Some(task), - context_id: 0, - }, - ); - } - - // In the test, the contents of the compact task may have been modified directly, while the contents of compact_task_assignment were not modified. - // So we pass the modified compact_task directly into the `report_compact_task_impl` - self.report_compact_tasks(vec![ReportTask { - task_id, - task_status: task_status as i32, - sorted_output_ssts, - table_stats_change: table_stats_change.unwrap_or_default(), - }]) - .await?; - Ok(()) - } - - pub fn metadata_manager(&self) -> &MetadataManager { - &self.metadata_manager - } - - fn notify_last_version_delta(&self, versioning: &Versioning) { - self.env - .notification_manager() - .notify_hummock_without_version( - Operation::Add, - Info::HummockVersionDeltas(risingwave_pb::hummock::HummockVersionDeltas { - version_deltas: vec![versioning - .hummock_version_deltas - .last_key_value() - .unwrap() - .1 - .to_protobuf()], - }), - ); - } - - fn notify_version_deltas(&self, versioning: &Versioning, last_version_id: u64) { - let start_version_id = last_version_id + 1; - let version_deltas = versioning - .hummock_version_deltas - .range(start_version_id..) - .map(|(_, delta)| delta.to_protobuf()) - .collect_vec(); - self.env - .notification_manager() - .notify_hummock_without_version( - Operation::Add, - Info::HummockVersionDeltas(risingwave_pb::hummock::HummockVersionDeltas { - version_deltas, - }), - ); - } - - fn notify_stats(&self, stats: &HummockVersionStats) { - self.env - .notification_manager() - .notify_frontend_without_version(Operation::Update, Info::HummockStats(stats.clone())); - } - - #[named] - pub fn hummock_timer_task(hummock_manager: Arc) -> (JoinHandle<()>, Sender<()>) { - use futures::{FutureExt, StreamExt}; - - let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); - let join_handle = tokio::spawn(async move { - const CHECK_PENDING_TASK_PERIOD_SEC: u64 = 300; - const STAT_REPORT_PERIOD_SEC: u64 = 20; - const COMPACTION_HEARTBEAT_PERIOD_SEC: u64 = 1; - - pub enum HummockTimerEvent { - GroupSplit, - CheckDeadTask, - Report, - CompactionHeartBeatExpiredCheck, - - DynamicCompactionTrigger, - SpaceReclaimCompactionTrigger, - TtlCompactionTrigger, - TombstoneCompactionTrigger, - - FullGc, - } - let mut check_compact_trigger_interval = - tokio::time::interval(Duration::from_secs(CHECK_PENDING_TASK_PERIOD_SEC)); - check_compact_trigger_interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - check_compact_trigger_interval.reset(); - - let check_compact_trigger = IntervalStream::new(check_compact_trigger_interval) - .map(|_| HummockTimerEvent::CheckDeadTask); - - let mut stat_report_interval = - tokio::time::interval(std::time::Duration::from_secs(STAT_REPORT_PERIOD_SEC)); - stat_report_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - stat_report_interval.reset(); - let stat_report_trigger = - IntervalStream::new(stat_report_interval).map(|_| HummockTimerEvent::Report); - - let mut compaction_heartbeat_interval = tokio::time::interval( - std::time::Duration::from_secs(COMPACTION_HEARTBEAT_PERIOD_SEC), - ); - compaction_heartbeat_interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - compaction_heartbeat_interval.reset(); - let compaction_heartbeat_trigger = IntervalStream::new(compaction_heartbeat_interval) - .map(|_| HummockTimerEvent::CompactionHeartBeatExpiredCheck); - - let mut min_trigger_interval = tokio::time::interval(Duration::from_secs( - hummock_manager.env.opts.periodic_compaction_interval_sec, - )); - min_trigger_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - min_trigger_interval.reset(); - let dynamic_tick_trigger = IntervalStream::new(min_trigger_interval) - .map(|_| HummockTimerEvent::DynamicCompactionTrigger); - - let mut min_space_reclaim_trigger_interval = - tokio::time::interval(Duration::from_secs( - hummock_manager - .env - .opts - .periodic_space_reclaim_compaction_interval_sec, - )); - min_space_reclaim_trigger_interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - min_space_reclaim_trigger_interval.reset(); - let space_reclaim_trigger = IntervalStream::new(min_space_reclaim_trigger_interval) - .map(|_| HummockTimerEvent::SpaceReclaimCompactionTrigger); - - let mut min_ttl_reclaim_trigger_interval = tokio::time::interval(Duration::from_secs( - hummock_manager - .env - .opts - .periodic_ttl_reclaim_compaction_interval_sec, - )); - min_ttl_reclaim_trigger_interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - min_ttl_reclaim_trigger_interval.reset(); - let ttl_reclaim_trigger = IntervalStream::new(min_ttl_reclaim_trigger_interval) - .map(|_| HummockTimerEvent::TtlCompactionTrigger); - - let mut full_gc_interval = tokio::time::interval(Duration::from_secs( - hummock_manager.env.opts.full_gc_interval_sec, - )); - full_gc_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - full_gc_interval.reset(); - let full_gc_trigger = - IntervalStream::new(full_gc_interval).map(|_| HummockTimerEvent::FullGc); - - let mut tombstone_reclaim_trigger_interval = - tokio::time::interval(Duration::from_secs( - hummock_manager - .env - .opts - .periodic_tombstone_reclaim_compaction_interval_sec, - )); - tombstone_reclaim_trigger_interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - tombstone_reclaim_trigger_interval.reset(); - let tombstone_reclaim_trigger = IntervalStream::new(tombstone_reclaim_trigger_interval) - .map(|_| HummockTimerEvent::TombstoneCompactionTrigger); - - let mut triggers: Vec> = vec![ - Box::pin(check_compact_trigger), - Box::pin(stat_report_trigger), - Box::pin(compaction_heartbeat_trigger), - Box::pin(dynamic_tick_trigger), - Box::pin(space_reclaim_trigger), - Box::pin(ttl_reclaim_trigger), - Box::pin(full_gc_trigger), - Box::pin(tombstone_reclaim_trigger), - ]; - - let periodic_check_split_group_interval_sec = hummock_manager - .env - .opts - .periodic_split_compact_group_interval_sec; - - if periodic_check_split_group_interval_sec > 0 { - let mut split_group_trigger_interval = tokio::time::interval(Duration::from_secs( - periodic_check_split_group_interval_sec, - )); - split_group_trigger_interval - .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); - - let split_group_trigger = IntervalStream::new(split_group_trigger_interval) - .map(|_| HummockTimerEvent::GroupSplit); - triggers.push(Box::pin(split_group_trigger)); - } - - let event_stream = select_all(triggers); - use futures::pin_mut; - pin_mut!(event_stream); - - let shutdown_rx_shared = shutdown_rx.shared(); - - tracing::info!( - "Hummock timer task tracing [GroupSplit interval {} sec] [CheckDeadTask interval {} sec] [Report interval {} sec] [CompactionHeartBeat interval {} sec]", - periodic_check_split_group_interval_sec, CHECK_PENDING_TASK_PERIOD_SEC, STAT_REPORT_PERIOD_SEC, COMPACTION_HEARTBEAT_PERIOD_SEC - ); - - loop { - let item = - futures::future::select(event_stream.next(), shutdown_rx_shared.clone()).await; - - match item { - Either::Left((event, _)) => { - if let Some(event) = event { - match event { - HummockTimerEvent::CheckDeadTask => { - if hummock_manager.env.opts.compaction_deterministic_test { - continue; - } - - hummock_manager.check_dead_task().await; - } - - HummockTimerEvent::GroupSplit => { - if hummock_manager.env.opts.compaction_deterministic_test { - continue; - } - - hummock_manager.on_handle_check_split_multi_group().await; - } - - HummockTimerEvent::Report => { - let ( - current_version, - id_to_config, - branched_sst, - version_stats, - ) = { - let versioning_guard = - read_lock!(hummock_manager.as_ref(), versioning).await; - - let configs = - hummock_manager.get_compaction_group_map().await; - let versioning_deref = versioning_guard; - ( - versioning_deref.current_version.clone(), - configs, - versioning_deref.branched_ssts.clone(), - versioning_deref.version_stats.clone(), - ) - }; - - if let Some(mv_id_to_all_table_ids) = hummock_manager - .metadata_manager - .get_job_id_to_internal_table_ids_mapping() - .await - { - trigger_mv_stat( - &hummock_manager.metrics, - &version_stats, - mv_id_to_all_table_ids, - ); - } - - for compaction_group_id in - get_compaction_group_ids(¤t_version) - { - let compaction_group_config = - &id_to_config[&compaction_group_id]; - - let group_levels = current_version - .get_compaction_group_levels( - compaction_group_config.group_id(), - ); - - trigger_split_stat( - &hummock_manager.metrics, - compaction_group_config.group_id(), - group_levels.member_table_ids.len(), - &branched_sst, - ); - - trigger_lsm_stat( - &hummock_manager.metrics, - compaction_group_config.compaction_config(), - group_levels, - compaction_group_config.group_id(), - ) - } - } - - HummockTimerEvent::CompactionHeartBeatExpiredCheck => { - let compactor_manager = - hummock_manager.compactor_manager.clone(); - - // TODO: add metrics to track expired tasks - // The cancel task has two paths - // 1. compactor heartbeat cancels the expired task based on task - // progress (meta + compactor) - // 2. meta periodically scans the task and performs a cancel on - // the meta side for tasks that are not updated by heartbeat - for task in compactor_manager.get_heartbeat_expired_tasks() { - if let Err(e) = hummock_manager - .cancel_compact_task( - task.task_id, - TaskStatus::HeartbeatCanceled, - ) - .await - { - tracing::error!( - task_id = task.task_id, - error = %e.as_report(), - "Attempt to remove compaction task due to elapsed heartbeat failed. We will continue to track its heartbeat - until we can successfully report its status", - ); - } - } - } - - HummockTimerEvent::DynamicCompactionTrigger => { - // Disable periodic trigger for compaction_deterministic_test. - if hummock_manager.env.opts.compaction_deterministic_test { - continue; - } - - hummock_manager - .on_handle_trigger_multi_group( - compact_task::TaskType::Dynamic, - ) - .await; - } - - HummockTimerEvent::SpaceReclaimCompactionTrigger => { - // Disable periodic trigger for compaction_deterministic_test. - if hummock_manager.env.opts.compaction_deterministic_test { - continue; - } - - hummock_manager - .on_handle_trigger_multi_group( - compact_task::TaskType::SpaceReclaim, - ) - .await; - } - - HummockTimerEvent::TtlCompactionTrigger => { - // Disable periodic trigger for compaction_deterministic_test. - if hummock_manager.env.opts.compaction_deterministic_test { - continue; - } - - hummock_manager - .on_handle_trigger_multi_group(compact_task::TaskType::Ttl) - .await; - } - - HummockTimerEvent::TombstoneCompactionTrigger => { - // Disable periodic trigger for compaction_deterministic_test. - if hummock_manager.env.opts.compaction_deterministic_test { - continue; - } - - hummock_manager - .on_handle_trigger_multi_group( - compact_task::TaskType::Tombstone, - ) - .await; - } - - HummockTimerEvent::FullGc => { - if hummock_manager - .start_full_gc(Duration::from_secs(3600)) - .is_ok() - { - tracing::info!("Start full GC from meta node."); - } - } - } - } - } - - Either::Right((_, _shutdown)) => { - tracing::info!("Hummock timer loop is stopped"); - break; - } - } - } - }); - (join_handle, shutdown_tx) - } - - #[named] - pub async fn check_dead_task(&self) { - const MAX_COMPACTION_L0_MULTIPLIER: u64 = 32; - const MAX_COMPACTION_DURATION_SEC: u64 = 20 * 60; - let (groups, configs) = { - let versioning_guard = read_lock!(self, versioning).await; - let g = versioning_guard - .current_version - .levels - .iter() - .map(|(id, group)| { - ( - *id, - group - .l0 - .as_ref() - .unwrap() - .sub_levels - .iter() - .map(|level| level.total_file_size) - .sum::(), - ) - }) - .collect_vec(); - let c = self.get_compaction_group_map().await; - (g, c) - }; - let mut slowdown_groups: HashMap = HashMap::default(); - { - for (group_id, l0_file_size) in groups { - let group = &configs[&group_id]; - if l0_file_size - > MAX_COMPACTION_L0_MULTIPLIER - * group.compaction_config.max_bytes_for_level_base - { - slowdown_groups.insert(group_id, l0_file_size); - } - } - } - if slowdown_groups.is_empty() { - return; - } - let mut pending_tasks: HashMap = HashMap::default(); - { - let compaction_guard = read_lock!(self, compaction).await; - for group_id in slowdown_groups.keys() { - if let Some(status) = compaction_guard.compaction_statuses.get(group_id) { - for (idx, level_handler) in status.level_handlers.iter().enumerate() { - let tasks = level_handler.get_pending_tasks().to_vec(); - if tasks.is_empty() { - continue; - } - for task in tasks { - pending_tasks.insert(task.task_id, (*group_id, idx, task)); - } - } - } - } - } - let task_ids = pending_tasks.keys().cloned().collect_vec(); - let task_infos = self - .compactor_manager - .check_tasks_status(&task_ids, Duration::from_secs(MAX_COMPACTION_DURATION_SEC)); - for (task_id, (compact_time, status)) in task_infos { - if status == TASK_NORMAL { - continue; - } - if let Some((group_id, level_id, task)) = pending_tasks.get(&task_id) { - let group_size = *slowdown_groups.get(group_id).unwrap(); - warn!("COMPACTION SLOW: the task-{} of group-{}(size: {}MB) level-{} has not finished after {:?}, {}, it may cause pending sstable files({:?}) blocking other task.", - task_id, *group_id,group_size / 1024 / 1024,*level_id, compact_time, status, task.ssts); - } - } - } - - fn collect_table_write_throughput(&self, table_stats: PbTableStatsMap) { - let mut table_infos = self.history_table_throughput.write(); - for (table_id, stat) in table_stats { - let throughput = (stat.total_value_size + stat.total_key_size) as u64; - let entry = table_infos.entry(table_id).or_default(); - entry.push_back(throughput); - if entry.len() > HISTORY_TABLE_INFO_STATISTIC_TIME { - entry.pop_front(); - } - } - } - - /// * For compaction group with only one single state-table, do not change it again. - /// * For state-table which only write less than `HISTORY_TABLE_INFO_WINDOW_SIZE` times, do not - /// change it. Because we need more statistic data to decide split strategy. - /// * For state-table with low throughput which write no more than - /// `min_table_split_write_throughput` data, never split it. - /// * For state-table whose size less than `min_table_split_size`, do not split it unless its - /// throughput keep larger than `table_write_throughput_threshold` for a long time. - /// * For state-table whose throughput less than `min_table_split_write_throughput`, do not - /// increase it size of base-level. - async fn on_handle_check_split_multi_group(&self) { - let params = self.env.system_params_reader().await; - let barrier_interval_ms = params.barrier_interval_ms() as u64; - let checkpoint_secs = std::cmp::max( - 1, - params.checkpoint_frequency() * barrier_interval_ms / 1000, - ); - let created_tables = match self.metadata_manager.get_created_table_ids().await { - Ok(created_tables) => created_tables, - Err(err) => { - tracing::warn!(error = %err.as_report(), "failed to fetch created table ids"); - return; - } - }; - let created_tables: HashSet = HashSet::from_iter(created_tables); - let table_write_throughput = self.history_table_throughput.read().clone(); - let mut group_infos = self.calculate_compaction_group_statistic().await; - group_infos.sort_by_key(|group| group.group_size); - group_infos.reverse(); - const SPLIT_BY_TABLE: u32 = 1; - - let mut group_to_table_vnode_partition = self.group_to_table_vnode_partition.read().clone(); - for group in &group_infos { - if group.table_statistic.len() == 1 { - // no need to handle the separate compaciton group - continue; - } - - let mut table_vnode_partition_mappoing = group_to_table_vnode_partition - .entry(group.group_id) - .or_default(); - - for (table_id, table_size) in &group.table_statistic { - let rule = self - .calculate_table_align_rule( - &table_write_throughput, - table_id, - table_size, - !created_tables.contains(table_id), - checkpoint_secs, - group.group_id, - group.group_size, - ) - .await; - - match rule { - TableAlignRule::NoOptimization => { - table_vnode_partition_mappoing.remove(table_id); - continue; - } - - TableAlignRule::SplitByTable(table_id) => { - if self.env.opts.hybird_partition_vnode_count > 0 { - table_vnode_partition_mappoing.insert(table_id, SPLIT_BY_TABLE); - } else { - table_vnode_partition_mappoing.remove(&table_id); - } - } - - TableAlignRule::SplitByVnode((table_id, vnode)) => { - if self.env.opts.hybird_partition_vnode_count > 0 { - table_vnode_partition_mappoing.insert(table_id, vnode); - } else { - table_vnode_partition_mappoing.remove(&table_id); - } - } - - TableAlignRule::SplitToDedicatedCg(( - new_group_id, - table_vnode_partition_count, - )) => { - let _ = table_vnode_partition_mappoing; // drop - group_to_table_vnode_partition - .insert(new_group_id, table_vnode_partition_count); - - table_vnode_partition_mappoing = group_to_table_vnode_partition - .entry(group.group_id) - .or_default(); - } - } - } - } - - tracing::trace!( - "group_to_table_vnode_partition {:?}", - group_to_table_vnode_partition - ); - - // batch update group_to_table_vnode_partition - *self.group_to_table_vnode_partition.write() = group_to_table_vnode_partition; - } - - /// dedicated event runtime for CPU/IO bound event - pub fn compaction_event_loop( - hummock_manager: Arc, - mut compactor_streams_change_rx: UnboundedReceiver<( - u32, - Streaming, - )>, - ) -> Vec<(JoinHandle<()>, Sender<()>)> { - let mut compactor_request_streams = FuturesUnordered::new(); - let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); - let (shutdown_tx_dedicated, shutdown_rx_dedicated) = tokio::sync::oneshot::channel(); - let shutdown_rx_shared = shutdown_rx.shared(); - let shutdown_rx_dedicated_shared = shutdown_rx_dedicated.shared(); - - let (tx, rx) = unbounded_channel(); - - let mut join_handle_vec = Vec::default(); - - let hummock_manager_dedicated = hummock_manager.clone(); - let compact_task_event_handler_join_handle = tokio::spawn(async move { - Self::compact_task_dedicated_event_handler( - hummock_manager_dedicated, - rx, - shutdown_rx_dedicated_shared, - ) - .await; - }); - - join_handle_vec.push(( - compact_task_event_handler_join_handle, - shutdown_tx_dedicated, - )); - - let join_handle = tokio::spawn(async move { - let push_stream = - |context_id: u32, - stream: Streaming, - compactor_request_streams: &mut FuturesUnordered<_>| { - let future = stream - .into_future() - .map(move |stream_future| (context_id, stream_future)); - - compactor_request_streams.push(future); - }; - - let mut event_loop_iteration_now = Instant::now(); - - loop { - let shutdown_rx_shared = shutdown_rx_shared.clone(); - let hummock_manager = hummock_manager.clone(); - hummock_manager - .metrics - .compaction_event_loop_iteration_latency - .observe(event_loop_iteration_now.elapsed().as_millis() as _); - event_loop_iteration_now = Instant::now(); - - tokio::select! { - _ = shutdown_rx_shared => { return; }, - - compactor_stream = compactor_streams_change_rx.recv() => { - if let Some((context_id, stream)) = compactor_stream { - tracing::info!("compactor {} enters the cluster", context_id); - push_stream(context_id, stream, &mut compactor_request_streams); - } - }, - - result = pending_on_none(compactor_request_streams.next()) => { - let mut compactor_alive = true; - - let (context_id, compactor_stream_req): (_, (std::option::Option>, _)) = result; - let (event, create_at, stream) = match compactor_stream_req { - (Some(Ok(req)), stream) => { - (req.event.unwrap(), req.create_at, stream) - } - - (Some(Err(err)), _stream) => { - tracing::warn!(error = %err.as_report(), "compactor stream {} poll with err, recv stream may be destroyed", context_id); - continue - } - - _ => { - tracing::warn!("compactor stream {} poll err, recv stream may be destroyed", context_id); - continue - }, - }; - - { - let consumed_latency_ms = SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("Clock may have gone backwards") - .as_millis() - as u64 - - create_at; - hummock_manager.metrics - .compaction_event_consumed_latency - .observe(consumed_latency_ms as _); - } - - match event { - RequestEvent::HeartBeat(HeartBeat { - progress, - }) => { - let compactor_manager = hummock_manager.compactor_manager.clone(); - let cancel_tasks = compactor_manager.update_task_heartbeats(&progress); - if let Some(compactor) = compactor_manager.get_compactor(context_id) { - // TODO: task cancellation can be batched - for task in cancel_tasks { - tracing::info!( - "Task with group_id {} task_id {} with context_id {} has expired due to lack of visible progress", - task.compaction_group_id, - task.task_id, - context_id, - ); - - if let Err(e) = - hummock_manager - .cancel_compact_task(task.task_id, TaskStatus::HeartbeatCanceled) - .await - { - tracing::error!( - task_id = task.task_id, - error = %e.as_report(), - "Attempt to remove compaction task due to elapsed heartbeat failed. We will continue to track its heartbeat - until we can successfully report its status." - ); - } - - // Forcefully cancel the task so that it terminates - // early on the compactor - // node. - let _ = compactor.cancel_task(task.task_id); - tracing::info!( - "CancelTask operation for task_id {} has been sent to node with context_id {}", - context_id, - task.task_id - ); - } - } else { - // Determine the validity of the compactor streaming rpc. When the compactor no longer exists in the manager, the stream will be removed. - // Tip: Connectivity to the compactor will be determined through the `send_event` operation. When send fails, it will be removed from the manager - compactor_alive = false; - } - }, - - RequestEvent::Register(_) => { - unreachable!() - } - - e @ (RequestEvent::PullTask(_) | RequestEvent::ReportTask(_)) => { - let _ = tx.send((context_id, e)); - } - } - - if compactor_alive { - push_stream(context_id, stream, &mut compactor_request_streams); - } else { - tracing::warn!("compactor stream {} error, send stream may be destroyed", context_id); - } - }, - } - } - }); - - join_handle_vec.push((join_handle, shutdown_tx)); - - join_handle_vec - } - - pub fn add_compactor_stream( - &self, - context_id: u32, - req_stream: Streaming, - ) { - self.compactor_streams_change_tx - .send((context_id, req_stream)) - .unwrap(); - } - - async fn on_handle_trigger_multi_group(&self, task_type: compact_task::TaskType) { - for cg_id in self.compaction_group_ids().await { - if let Err(e) = self.compaction_state.try_sched_compaction(cg_id, task_type) { - tracing::warn!( - error = %e.as_report(), - "Failed to schedule {:?} compaction for compaction group {}", - task_type, - cg_id, - ); - } - } - } - - pub async fn auto_pick_compaction_group_and_type( - &self, - ) -> Option<(CompactionGroupId, compact_task::TaskType)> { - let mut compaction_group_ids = self.compaction_group_ids().await; - compaction_group_ids.shuffle(&mut thread_rng()); - - for cg_id in compaction_group_ids { - if let Some(pick_type) = self.compaction_state.auto_pick_type(cg_id) { - return Some((cg_id, pick_type)); - } - } - - None - } - - /// This method will return all compaction group id in a random order and task type. If there are any group block by `write_limit`, it will return a single array with `TaskType::Emergency`. - /// If these groups get different task-type, it will return all group id with `TaskType::Dynamic` if the first group get `TaskType::Dynamic`, otherwise it will return the single group with other task type. - pub async fn auto_pick_compaction_groups_and_type( - &self, - ) -> (Vec, compact_task::TaskType) { - let mut compaction_group_ids = self.compaction_group_ids().await; - compaction_group_ids.shuffle(&mut thread_rng()); - - let mut normal_groups = vec![]; - for cg_id in compaction_group_ids { - if let Some(pick_type) = self.compaction_state.auto_pick_type(cg_id) { - if pick_type == TaskType::Dynamic { - normal_groups.push(cg_id); - } else if normal_groups.is_empty() { - return (vec![cg_id], pick_type); - } - } - } - (normal_groups, TaskType::Dynamic) - } - - async fn calculate_table_align_rule( - &self, - table_write_throughput: &HashMap>, - table_id: &u32, - table_size: &u64, - is_creating_table: bool, - checkpoint_secs: u64, - parent_group_id: u64, - group_size: u64, - ) -> TableAlignRule { - let default_group_id: CompactionGroupId = StaticCompactionGroupId::StateDefault.into(); - let mv_group_id: CompactionGroupId = StaticCompactionGroupId::MaterializedView.into(); - let partition_vnode_count = self.env.opts.partition_vnode_count; - let hybrid_vnode_count: u32 = self.env.opts.hybird_partition_vnode_count; - let window_size = HISTORY_TABLE_INFO_STATISTIC_TIME / (checkpoint_secs as usize); - - let mut is_high_write_throughput = false; - let mut is_low_write_throughput = true; - if let Some(history) = table_write_throughput.get(table_id) { - if !is_creating_table { - if history.len() >= window_size { - is_high_write_throughput = history.iter().all(|throughput| { - *throughput / checkpoint_secs - > self.env.opts.table_write_throughput_threshold - }); - is_low_write_throughput = history.iter().any(|throughput| { - *throughput / checkpoint_secs - < self.env.opts.min_table_split_write_throughput - }); - } - } else { - // For creating table, relax the checking restrictions to make the data alignment behavior more sensitive. - let sum = history.iter().sum::(); - is_low_write_throughput = sum - < self.env.opts.min_table_split_write_throughput - * history.len() as u64 - * checkpoint_secs; - } - } - - let state_table_size = *table_size; - let result = { - // When in a hybrid compaction group, data from multiple state tables may exist in a single sst, and in order to make the data in the sub level more aligned, a proactive cut is made for the data. - // https://github.com/risingwavelabs/risingwave/issues/13037 - // 1. In some scenario (like backfill), the creating state_table / mv may have high write throughput (creating table ). Therefore, we relax the limit of `is_low_write_throughput` and partition the table with high write throughput by vnode to improve the parallel efficiency of compaction. - // Add: creating table is not allowed to be split - // 2. For table with low throughput, partition by table_id to minimize amplification. - // 3. When the write mode is changed (the above conditions are not met), the default behavior is restored - if !is_low_write_throughput { - TableAlignRule::SplitByVnode((*table_id, hybrid_vnode_count)) - } else if state_table_size > self.env.opts.cut_table_size_limit { - TableAlignRule::SplitByTable(*table_id) - } else { - TableAlignRule::NoOptimization - } - }; - - // 1. Avoid splitting a creating table - // 2. Avoid splitting a is_low_write_throughput creating table - // 3. Avoid splitting a non-high throughput medium-sized table - if is_creating_table - || (is_low_write_throughput) - || (state_table_size < self.env.opts.min_table_split_size && !is_high_write_throughput) - { - return result; - } - - // do not split a large table and a small table because it would increase IOPS - // of small table. - if parent_group_id != default_group_id && parent_group_id != mv_group_id { - let rest_group_size = group_size - state_table_size; - if rest_group_size < state_table_size - && rest_group_size < self.env.opts.min_table_split_size - { - return result; - } - } - - let ret = self - .move_state_table_to_compaction_group( - parent_group_id, - &[*table_id], - None, - partition_vnode_count, - ) - .await; - match ret { - Ok((new_group_id, table_vnode_partition_count)) => { - tracing::info!("move state table [{}] from group-{} to group-{} success table_vnode_partition_count {:?}", table_id, parent_group_id, new_group_id, table_vnode_partition_count); - return TableAlignRule::SplitToDedicatedCg(( - new_group_id, - table_vnode_partition_count, - )); - } - Err(e) => { - tracing::info!( - error = %e.as_report(), - "failed to move state table [{}] from group-{}", - table_id, - parent_group_id, - ) - } - } - - TableAlignRule::NoOptimization - } - - async fn initial_compaction_group_config_after_load( - &self, - versioning_guard: &mut RwLockWriteGuard<'_, Versioning>, - ) -> Result<()> { - // 1. Due to version compatibility, we fix some of the configuration of older versions after hummock starts. - let current_version = &versioning_guard.current_version; - let all_group_ids = get_compaction_group_ids(current_version); - let mut configs = self - .compaction_group_manager - .write() - .await - .get_or_insert_compaction_group_configs(&all_group_ids.collect_vec()) - .await?; - - // We've already lowered the default limit for write limit in PR-12183, and to prevent older clusters from continuing to use the outdated configuration, we've introduced a new logic to rewrite it in a uniform way. - let mut rewrite_cg_ids = vec![]; - let mut restore_cg_to_partition_vnode: HashMap> = - HashMap::default(); - for (cg_id, compaction_group_config) in &mut configs { - // update write limit - let relaxed_default_write_stop_level_count = 1000; - if compaction_group_config - .compaction_config - .level0_sub_level_compact_level_count - == relaxed_default_write_stop_level_count - { - rewrite_cg_ids.push(*cg_id); - } - - if let Some(levels) = current_version.levels.get(cg_id) { - if levels.member_table_ids.len() == 1 { - restore_cg_to_partition_vnode.insert( - *cg_id, - vec![( - levels.member_table_ids[0], - compaction_group_config - .compaction_config - .split_weight_by_vnode, - )] - .into_iter() - .collect(), - ); - } - } - } - - if !rewrite_cg_ids.is_empty() { - tracing::info!("Compaction group {:?} configs rewrite ", rewrite_cg_ids); - - // update meta store - let result = self - .compaction_group_manager - .write() - .await - .update_compaction_config( - &rewrite_cg_ids, - &[ - mutable_config::MutableConfig::Level0StopWriteThresholdSubLevelNumber( - compaction_config::level0_stop_write_threshold_sub_level_number(), - ), - ], - ) - .await?; - - // update memory - for new_config in result { - configs.insert(new_config.group_id(), new_config); - } - } - - versioning_guard.write_limit = - calc_new_write_limits(configs, HashMap::new(), &versioning_guard.current_version); - trigger_write_stop_stats(&self.metrics, &versioning_guard.write_limit); - tracing::debug!("Hummock stopped write: {:#?}", versioning_guard.write_limit); - - { - // 2. Restore the memory data structure according to the memory of the compaction group config. - let mut group_to_table_vnode_partition = self.group_to_table_vnode_partition.write(); - for (cg_id, table_vnode_partition) in restore_cg_to_partition_vnode { - group_to_table_vnode_partition.insert(cg_id, table_vnode_partition); - } - } - - Ok(()) - } -} - -// This structure describes how hummock handles sst switching in a compaction group. A better sst cut will result in better data alignment, which in turn will improve the efficiency of the compaction. -// By adopting certain rules, a better sst cut will lead to better data alignment and thus improve the efficiency of the compaction. -pub enum TableAlignRule { - // The table_id is not optimized for alignment. - NoOptimization, - // Move the table_id to a separate compaction group. Currently, the system only supports separate compaction with one table. - SplitToDedicatedCg((CompactionGroupId, BTreeMap)), - // In the current group, partition the table's data according to the granularity of the vnode. - SplitByVnode((StateTableId, u32)), - // In the current group, partition the table's data at the granularity of the table. - SplitByTable(StateTableId), -} - -fn drop_sst( - branched_ssts: &mut BTreeMapTransactionWrapper<'_, HummockSstableObjectId, BranchedSstInfo>, - group_id: CompactionGroupId, - object_id: HummockSstableObjectId, - sst_id: HummockSstableId, -) -> bool { - match branched_ssts.get_mut(object_id) { - Some(mut entry) => { - // if group_id not exist, it would not pass the stale check before. - let removed_sst_id = entry.get(&group_id).unwrap(); - assert_eq!(*removed_sst_id, sst_id); - entry.remove(&group_id); - if entry.is_empty() { - branched_ssts.remove(object_id); - true - } else { - false - } - } - None => true, - } -} - -fn gen_version_delta<'a>( - txn: &mut BTreeMapTransactionWrapper<'a, HummockVersionId, HummockVersionDelta>, - branched_ssts: &mut BTreeMapTransactionWrapper<'a, HummockSstableObjectId, BranchedSstInfo>, - old_version: &HummockVersion, - compact_task: &CompactTask, - deterministic_mode: bool, -) -> HummockVersionDelta { - let trivial_move = CompactStatus::is_trivial_move_task(compact_task); - - let mut version_delta = HummockVersionDelta { - id: old_version.id + 1, - prev_id: old_version.id, - max_committed_epoch: old_version.max_committed_epoch, - trivial_move, - ..Default::default() - }; - let group_deltas = &mut version_delta - .group_deltas - .entry(compact_task.compaction_group_id) - .or_default() - .group_deltas; - let mut removed_table_ids_map: BTreeMap> = BTreeMap::default(); - - for level in &compact_task.input_ssts { - let level_idx = level.level_idx; - let mut removed_table_ids = level - .table_infos - .iter() - .map(|sst| { - let object_id = sst.get_object_id(); - let sst_id = sst.get_sst_id(); - if !trivial_move { - drop_sst( - branched_ssts, - compact_task.compaction_group_id, - object_id, - sst_id, - ); - } - sst_id - }) - .collect_vec(); - - removed_table_ids_map - .entry(level_idx) - .or_default() - .append(&mut removed_table_ids); - } - - for (level_idx, removed_table_ids) in removed_table_ids_map { - let group_delta = GroupDelta { - delta_type: Some(DeltaType::IntraLevel(IntraLevelDelta { - level_idx, - removed_table_ids, - ..Default::default() - })), - }; - group_deltas.push(group_delta); - } - - let group_delta = GroupDelta { - delta_type: Some(DeltaType::IntraLevel(IntraLevelDelta { - level_idx: compact_task.target_level, - inserted_table_infos: compact_task.sorted_output_ssts.clone(), - l0_sub_level_id: compact_task.target_sub_level_id, - vnode_partition_count: compact_task.split_weight_by_vnode, - ..Default::default() - })), - }; - group_deltas.push(group_delta); - version_delta.safe_epoch = std::cmp::max(old_version.safe_epoch, compact_task.watermark); - - // Don't persist version delta generated by compaction to meta store in deterministic mode. - // Because it will override existing version delta that has same ID generated in the data - // ingestion phase. - if !deterministic_mode { - txn.insert(version_delta.id, version_delta.clone()); - } - - version_delta -} - -async fn write_exclusive_cluster_id( - state_store_dir: &str, - cluster_id: ClusterId, - object_store: ObjectStoreRef, -) -> Result<()> { - const CLUSTER_ID_DIR: &str = "cluster_id"; - const CLUSTER_ID_NAME: &str = "0"; - let cluster_id_dir = format!("{}/{}/", state_store_dir, CLUSTER_ID_DIR); - let cluster_id_full_path = format!("{}{}", cluster_id_dir, CLUSTER_ID_NAME); - match object_store.read(&cluster_id_full_path, ..).await { - Ok(stored_cluster_id) => { - let stored_cluster_id = String::from_utf8(stored_cluster_id.to_vec()).unwrap(); - if cluster_id.deref() == stored_cluster_id { - return Ok(()); - } - - Err(ObjectError::internal(format!( - "Data directory is already used by another cluster with id {:?}, path {}.", - stored_cluster_id, cluster_id_full_path, - )) - .into()) - } - Err(e) => { - if e.is_object_not_found_error() { - object_store - .upload(&cluster_id_full_path, Bytes::from(String::from(cluster_id))) - .await?; - return Ok(()); + Err(e) => { + if e.is_object_not_found_error() { + object_store + .upload(&cluster_id_full_path, Bytes::from(String::from(cluster_id))) + .await?; + return Ok(()); } Err(e.into()) } } } - -fn init_selectors() -> HashMap> { - let mut compaction_selectors: HashMap> = - HashMap::default(); - compaction_selectors.insert( - compact_task::TaskType::Dynamic, - Box::::default(), - ); - compaction_selectors.insert( - compact_task::TaskType::SpaceReclaim, - Box::::default(), - ); - compaction_selectors.insert( - compact_task::TaskType::Ttl, - Box::::default(), - ); - compaction_selectors.insert( - compact_task::TaskType::Tombstone, - Box::::default(), - ); - compaction_selectors -} - -type CompactionRequestChannelItem = (CompactionGroupId, compact_task::TaskType); -use risingwave_hummock_sdk::table_watermark::TableWatermarks; -use risingwave_hummock_sdk::version::HummockVersion; -use tokio::sync::mpsc::error::SendError; - -use super::compaction::CompactionSelector; -use crate::hummock::manager::checkpoint::HummockVersionCheckpoint; -use crate::hummock::sequence::next_sstable_object_id; - -#[derive(Debug, Default)] -pub struct CompactionState { - scheduled: Mutex>, -} - -impl CompactionState { - pub fn new() -> Self { - Self { - scheduled: Default::default(), - } - } - - /// Enqueues only if the target is not yet in queue. - pub fn try_sched_compaction( - &self, - compaction_group: CompactionGroupId, - task_type: TaskType, - ) -> std::result::Result> { - let mut guard = self.scheduled.lock(); - let key = (compaction_group, task_type); - if guard.contains(&key) { - return Ok(false); - } - guard.insert(key); - Ok(true) - } - - pub fn unschedule( - &self, - compaction_group: CompactionGroupId, - task_type: compact_task::TaskType, - ) { - self.scheduled.lock().remove(&(compaction_group, task_type)); - } - - pub fn auto_pick_type(&self, group: CompactionGroupId) -> Option { - let guard = self.scheduled.lock(); - if guard.contains(&(group, compact_task::TaskType::Dynamic)) { - Some(compact_task::TaskType::Dynamic) - } else if guard.contains(&(group, compact_task::TaskType::SpaceReclaim)) { - Some(compact_task::TaskType::SpaceReclaim) - } else if guard.contains(&(group, compact_task::TaskType::Ttl)) { - Some(compact_task::TaskType::Ttl) - } else if guard.contains(&(group, compact_task::TaskType::Tombstone)) { - Some(compact_task::TaskType::Tombstone) - } else { - None - } - } -} diff --git a/src/meta/src/hummock/manager/tests.rs b/src/meta/src/hummock/manager/tests.rs index 65ddef44af8a1..c2abc138e4944 100644 --- a/src/meta/src/hummock/manager/tests.rs +++ b/src/meta/src/hummock/manager/tests.rs @@ -12,18 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg(test)] + use std::borrow::Borrow; use std::cmp::Ordering; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::sync::Arc; use itertools::Itertools; use prometheus::Registry; +use risingwave_common::catalog::TableId; use risingwave_common::util::epoch::{test_epoch, EpochExt, INVALID_EPOCH}; use risingwave_hummock_sdk::compact::compact_task_to_string; -use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - get_compaction_group_ssts, BranchedSstInfo, -}; +use risingwave_hummock_sdk::compaction_group::hummock_version_ext::get_compaction_group_ssts; use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId; use risingwave_hummock_sdk::table_stats::{to_prost_table_stats_map, TableStats, TableStatsMap}; use risingwave_hummock_sdk::version::HummockVersion; @@ -46,7 +47,7 @@ use crate::hummock::compaction::selector::{ }; use crate::hummock::error::Error; use crate::hummock::test_utils::*; -use crate::hummock::{CommitEpochInfo, HummockManager, HummockManagerRef}; +use crate::hummock::{HummockManager, HummockManagerRef}; use crate::manager::{MetaSrvEnv, MetaStoreImpl, WorkerId}; use crate::model::MetadataModel; use crate::rpc::metrics::MetaMetrics; @@ -100,7 +101,7 @@ fn get_compaction_group_object_ids( } async fn list_pinned_snapshot_from_meta_store(env: &MetaSrvEnv) -> Vec { - match env.meta_store() { + match env.meta_store_ref() { MetaStoreImpl::Kv(meta_store) => HummockPinnedSnapshot::list(meta_store).await.unwrap(), MetaStoreImpl::Sql(sql_meta_store) => { use risingwave_meta_model_v2::hummock_pinned_snapshot; @@ -117,7 +118,7 @@ async fn list_pinned_snapshot_from_meta_store(env: &MetaSrvEnv) -> Vec Vec { - match env.meta_store() { + match env.meta_store_ref() { MetaStoreImpl::Kv(meta_store) => HummockPinnedVersion::list(meta_store).await.unwrap(), MetaStoreImpl::Sql(sql_meta_store) => { use risingwave_meta_model_v2::hummock_pinned_version; @@ -590,7 +591,7 @@ async fn test_hummock_manager_basic() { ); } // objects_to_delete is always empty because no compaction is ever invoked. - assert!(hummock_manager.get_objects_to_delete().await.is_empty()); + assert!(hummock_manager.get_objects_to_delete().is_empty()); assert_eq!( hummock_manager .delete_version_deltas(usize::MAX) @@ -602,7 +603,7 @@ async fn test_hummock_manager_basic() { hummock_manager.create_version_checkpoint(1).await.unwrap(), commit_log_count + register_log_count ); - assert!(hummock_manager.get_objects_to_delete().await.is_empty()); + assert!(hummock_manager.get_objects_to_delete().is_empty()); assert_eq!( hummock_manager .delete_version_deltas(usize::MAX) @@ -801,10 +802,7 @@ async fn test_invalid_sst_id() { .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), WorkerId::MAX)) .collect(); let error = hummock_manager - .commit_epoch( - epoch, - CommitEpochInfo::for_test(ssts.clone(), sst_to_worker), - ) + .commit_epoch_for_test(epoch, ssts.clone(), sst_to_worker) .await .unwrap_err(); assert!(matches!(error, Error::InvalidSst(1))); @@ -814,7 +812,7 @@ async fn test_invalid_sst_id() { .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), context_id)) .collect(); hummock_manager - .commit_epoch(epoch, CommitEpochInfo::for_test(ssts, sst_to_worker)) + .commit_epoch_for_test(epoch, ssts, sst_to_worker) .await .unwrap(); } @@ -1127,7 +1125,7 @@ async fn test_extend_objects_to_delete() { .map(|s| s.get_object_id()) .chain(max_committed_object_id + 1..=max_committed_object_id + orphan_sst_num) .collect_vec(); - assert!(hummock_manager.get_objects_to_delete().await.is_empty()); + assert!(hummock_manager.get_objects_to_delete().is_empty()); assert_eq!( hummock_manager .extend_objects_to_delete_from_scan(&all_object_ids) @@ -1135,7 +1133,7 @@ async fn test_extend_objects_to_delete() { orphan_sst_num as usize ); assert_eq!( - hummock_manager.get_objects_to_delete().await.len(), + hummock_manager.get_objects_to_delete().len(), orphan_sst_num as usize ); @@ -1145,7 +1143,7 @@ async fn test_extend_objects_to_delete() { 6 ); assert_eq!( - hummock_manager.get_objects_to_delete().await.len(), + hummock_manager.get_objects_to_delete().len(), orphan_sst_num as usize ); // since version1 is still pinned, the sst removed in compaction can not be reclaimed. @@ -1155,10 +1153,10 @@ async fn test_extend_objects_to_delete() { .await, orphan_sst_num as usize ); - let objects_to_delete = hummock_manager.get_objects_to_delete().await; + let objects_to_delete = hummock_manager.get_objects_to_delete(); assert_eq!(objects_to_delete.len(), orphan_sst_num as usize); let pinned_version2: HummockVersion = hummock_manager.pin_version(context_id).await.unwrap(); - let objects_to_delete = hummock_manager.get_objects_to_delete().await; + let objects_to_delete = hummock_manager.get_objects_to_delete(); assert_eq!( objects_to_delete.len(), orphan_sst_num as usize, @@ -1169,7 +1167,7 @@ async fn test_extend_objects_to_delete() { .unpin_version_before(context_id, pinned_version2.id) .await .unwrap(); - let objects_to_delete = hummock_manager.get_objects_to_delete().await; + let objects_to_delete = hummock_manager.get_objects_to_delete(); assert_eq!( objects_to_delete.len(), orphan_sst_num as usize, @@ -1184,13 +1182,14 @@ async fn test_extend_objects_to_delete() { .await, orphan_sst_num as usize ); - let objects_to_delete = hummock_manager.get_objects_to_delete().await; + let objects_to_delete = hummock_manager.get_objects_to_delete(); assert_eq!(objects_to_delete.len(), orphan_sst_num as usize); let new_epoch = pinned_version2.max_committed_epoch.next_epoch(); hummock_manager - .commit_epoch( + .commit_epoch_for_test( new_epoch, - CommitEpochInfo::for_test(Vec::::new(), Default::default()), + Vec::::new(), + Default::default(), ) .await .unwrap(); @@ -1208,7 +1207,7 @@ async fn test_extend_objects_to_delete() { .await, orphan_sst_num as usize + 3 ); - let objects_to_delete = hummock_manager.get_objects_to_delete().await; + let objects_to_delete = hummock_manager.get_objects_to_delete(); assert_eq!(objects_to_delete.len(), orphan_sst_num as usize + 3); } @@ -1261,7 +1260,7 @@ async fn test_version_stats() { .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), worker_node.id)) .collect(); hummock_manager - .commit_epoch(epoch, CommitEpochInfo::for_test(ssts, sst_to_worker)) + .commit_epoch_for_test(epoch, ssts, sst_to_worker) .await .unwrap(); @@ -1339,11 +1338,11 @@ async fn test_split_compaction_group_on_commit() { let (_env, hummock_manager, _, worker_node) = setup_compute_env(80).await; let context_id = worker_node.id; hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 3)]) + .register_table_ids_for_test(&[(101, 3)]) .await .unwrap(); let sst_1 = ExtendedSstableInfo { @@ -1360,10 +1359,7 @@ async fn test_split_compaction_group_on_commit() { table_stats: Default::default(), }; hummock_manager - .commit_epoch( - 30, - CommitEpochInfo::for_test(vec![sst_1], HashMap::from([(10, context_id)])), - ) + .commit_epoch_for_test(30, vec![sst_1], HashMap::from([(10, context_id)])) .await .unwrap(); let current_version = hummock_manager.get_current_version().await; @@ -1378,51 +1374,22 @@ async fn test_split_compaction_group_on_commit() { ); assert_eq!( current_version - .get_compaction_group_levels(2) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(2) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), vec![100] ); assert_eq!( current_version - .get_compaction_group_levels(3) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(3) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), vec![101] ); - let branched_ssts = hummock_manager - .versioning - .read(&["", "", ""]) - .await - .branched_ssts - .clone(); - assert_eq!(branched_ssts.len(), 1); - assert_eq!(branched_ssts.values().next().unwrap().len(), 2); - assert_ne!( - branched_ssts - .values() - .next() - .unwrap() - .get(&2) - .cloned() - .unwrap(), - branched_ssts - .values() - .next() - .unwrap() - .get(&3) - .cloned() - .unwrap(), - ); -} - -async fn get_branched_ssts( - hummock_manager: &HummockManager, -) -> BTreeMap { - hummock_manager - .versioning - .read(&["", "", ""]) - .await - .branched_ssts - .clone() } #[tokio::test] @@ -1460,11 +1427,11 @@ async fn test_split_compaction_group_on_demand_basic() { ); hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 2)]) + .register_table_ids_for_test(&[(101, 2)]) .await .unwrap(); let sst_1 = ExtendedSstableInfo { @@ -1502,12 +1469,10 @@ async fn test_split_compaction_group_on_demand_basic() { table_stats: Default::default(), }; hummock_manager - .commit_epoch( + .commit_epoch_for_test( 30, - CommitEpochInfo::for_test( - vec![sst_1, sst_2], - HashMap::from([(10, context_id), (11, context_id)]), - ), + vec![sst_1, sst_2], + HashMap::from([(10, context_id), (11, context_id)]), ) .await .unwrap(); @@ -1524,7 +1489,7 @@ async fn test_split_compaction_group_on_demand_basic() { // Now group 2 has member tables [100,101,102], so split [100, 101] can succeed even though // there is no data of 102. hummock_manager - .register_table_ids(&[(102, 2)]) + .register_table_ids_for_test(&[(102, 2)]) .await .unwrap(); @@ -1546,31 +1511,23 @@ async fn test_split_compaction_group_on_demand_basic() { ); assert_eq!( current_version - .get_compaction_group_levels(2) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(2) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), vec![102] ); assert_eq!( current_version - .get_compaction_group_levels(new_group_id) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(new_group_id) + .iter() + .map(|table_id| table_id.table_id) + .sorted() + .collect_vec(), vec![100, 101] ); - let branched_ssts = get_branched_ssts(&hummock_manager).await; - assert_eq!(branched_ssts.len(), 2); - for object_id in [10, 11] { - assert_eq!(branched_ssts.get(&object_id).unwrap().len(), 2); - assert_ne!( - branched_ssts - .get(&object_id) - .unwrap() - .get(&new_group_id) - .cloned() - .unwrap(), - object_id, - "trivial adjust should also generate a new SST id" - ); - } } #[tokio::test] @@ -1591,18 +1548,15 @@ async fn test_split_compaction_group_on_demand_non_trivial() { table_stats: Default::default(), }; hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 2)]) + .register_table_ids_for_test(&[(101, 2)]) .await .unwrap(); hummock_manager - .commit_epoch( - 30, - CommitEpochInfo::for_test(vec![sst_1], HashMap::from([(10, context_id)])), - ) + .commit_epoch_for_test(30, vec![sst_1], HashMap::from([(10, context_id)])) .await .unwrap(); @@ -1625,30 +1579,22 @@ async fn test_split_compaction_group_on_demand_non_trivial() { ); assert_eq!( current_version - .get_compaction_group_levels(2) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(2) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), vec![101] ); assert_eq!( current_version - .get_compaction_group_levels(new_group_id) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(new_group_id) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), vec![100] ); - let branched_ssts = get_branched_ssts(&hummock_manager).await; - assert_eq!(branched_ssts.len(), 1); - assert_eq!(branched_ssts.get(&10).unwrap().len(), 2); - let sst_ids = branched_ssts.get(&10).unwrap().get(&2).cloned().unwrap(); - assert_ne!(sst_ids, 10); - assert_ne!( - branched_ssts - .get(&10) - .unwrap() - .get(&new_group_id) - .cloned() - .unwrap(), - sst_ids, - ); } #[tokio::test] @@ -1667,11 +1613,11 @@ async fn test_split_compaction_group_trivial_expired() { hummock_manager.compactor_manager.add_compactor(context_id); hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 2)]) + .register_table_ids_for_test(&[(101, 2)]) .await .unwrap(); let sst_1 = ExtendedSstableInfo { @@ -1715,17 +1661,15 @@ async fn test_split_compaction_group_trivial_expired() { sst_4.sst_info.sst_id = 9; sst_4.sst_info.object_id = 9; hummock_manager - .commit_epoch( + .commit_epoch_for_test( 30, - CommitEpochInfo::for_test( - vec![sst_1, sst_2, sst_3, sst_4], - HashMap::from([ - (10, context_id), - (11, context_id), - (9, context_id), - (8, context_id), - ]), - ), + vec![sst_1, sst_2, sst_3, sst_4], + HashMap::from([ + (10, context_id), + (11, context_id), + (9, context_id), + (8, context_id), + ]), ) .await .unwrap(); @@ -1733,7 +1677,7 @@ async fn test_split_compaction_group_trivial_expired() { // Now group 2 has member tables [100,101,102], so split [100, 101] can succeed even though // there is no data of 102. hummock_manager - .register_table_ids(&[(102, 2)]) + .register_table_ids_for_test(&[(102, 2)]) .await .unwrap(); let task = hummock_manager @@ -1762,14 +1706,21 @@ async fn test_split_compaction_group_trivial_expired() { assert!(new_group_id > StaticCompactionGroupId::End as u64); assert_eq!( current_version - .get_compaction_group_levels(2) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(2) + .iter() + .map(|table_id| table_id.table_id) + .sorted() + .collect_vec(), vec![101, 102] ); assert_eq!( current_version - .get_compaction_group_levels(new_group_id) - .member_table_ids, + .state_table_info + .compaction_group_member_table_ids(new_group_id) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), vec![100] ); @@ -1829,11 +1780,11 @@ async fn test_split_compaction_group_on_demand_bottom_levels() { let context_id = worker_node.id; hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 2)]) + .register_table_ids_for_test(&[(101, 2)]) .await .unwrap(); @@ -1855,10 +1806,7 @@ async fn test_split_compaction_group_on_demand_bottom_levels() { table_stats: Default::default(), }; hummock_manager - .commit_epoch( - 30, - CommitEpochInfo::for_test(vec![sst_1.clone()], HashMap::from([(10, context_id)])), - ) + .commit_epoch_for_test(30, vec![sst_1.clone()], HashMap::from([(10, context_id)])) .await .unwrap(); // Construct data via manual compaction @@ -1927,14 +1875,6 @@ async fn test_split_compaction_group_on_demand_bottom_levels() { 2 ); - let branched_ssts = hummock_manager.get_branched_ssts_info().await; - // object-11 and object-12 - assert_eq!(branched_ssts.len(), 2); - let info = branched_ssts.get(&11).unwrap(); - assert_eq!( - info.keys().sorted().cloned().collect_vec(), - vec![2, new_group_id] - ); assert_eq!( current_version.get_compaction_group_levels(2).levels[base_level - 1].table_infos[0] .object_id, @@ -1977,11 +1917,11 @@ async fn test_compaction_task_expiration_due_to_split_group() { let context_id = worker_node.id; hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 2)]) + .register_table_ids_for_test(&[(101, 2)]) .await .unwrap(); let sst_1 = ExtendedSstableInfo { @@ -2019,12 +1959,10 @@ async fn test_compaction_task_expiration_due_to_split_group() { table_stats: Default::default(), }; hummock_manager - .commit_epoch( + .commit_epoch_for_test( 30, - CommitEpochInfo::for_test( - vec![sst_1, sst_2], - HashMap::from([(10, context_id), (11, context_id)]), - ), + vec![sst_1, sst_2], + HashMap::from([(10, context_id), (11, context_id)]), ) .await .unwrap(); @@ -2068,23 +2006,20 @@ async fn test_move_tables_between_compaction_group() { let context_id = worker_node.id; hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 2)]) + .register_table_ids_for_test(&[(101, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(102, 2)]) + .register_table_ids_for_test(&[(102, 2)]) .await .unwrap(); let sst_1 = gen_extend_sstable_info(10, 2, 1, vec![100, 101, 102]); hummock_manager - .commit_epoch( - 30, - CommitEpochInfo::for_test(vec![sst_1.clone()], HashMap::from([(10, context_id)])), - ) + .commit_epoch_for_test(30, vec![sst_1.clone()], HashMap::from([(10, context_id)])) .await .unwrap(); // Construct data via manual compaction @@ -2107,10 +2042,7 @@ async fn test_move_tables_between_compaction_group() { .unwrap()); let sst_2 = gen_extend_sstable_info(14, 2, 1, vec![101, 102]); hummock_manager - .commit_epoch( - 31, - CommitEpochInfo::for_test(vec![sst_2.clone()], HashMap::from([(14, context_id)])), - ) + .commit_epoch_for_test(31, vec![sst_2.clone()], HashMap::from([(14, context_id)])) .await .unwrap(); let current_version = hummock_manager.get_current_version().await; @@ -2140,12 +2072,6 @@ async fn test_move_tables_between_compaction_group() { assert_eq!(level.table_infos[0].table_ids, vec![100]); assert_eq!(level.table_infos[1].table_ids, vec![100, 101]); assert_eq!(level.table_infos.len(), 2); - let branched_ssts = hummock_manager.get_branched_ssts_info().await; - // object-id 11 and 12. - assert_eq!(branched_ssts.len(), 2); - let info = branched_ssts.get(&12).unwrap(); - let groups = info.keys().sorted().cloned().collect_vec(); - assert_eq!(groups, vec![2, new_group_id]); let mut selector: Box = Box::::default(); @@ -2155,9 +2081,9 @@ async fn test_move_tables_between_compaction_group() { .await .unwrap() .unwrap(); - assert_eq!(compaction_task.existing_table_ids, vec![101, 102]); assert_eq!(compaction_task.input_ssts[0].table_infos.len(), 1); assert_eq!(compaction_task.input_ssts[0].table_infos[0].object_id, 12); + assert_eq!(compaction_task.existing_table_ids, vec![101]); let ret = hummock_manager .report_compact_task( @@ -2169,9 +2095,6 @@ async fn test_move_tables_between_compaction_group() { .await .unwrap(); assert!(ret); - let branched_ssts = hummock_manager.get_branched_ssts_info().await; - // there is still left one sst for object-12 in branched-sst. - assert_eq!(branched_ssts.len(), 2); } #[tokio::test] @@ -2259,19 +2182,16 @@ async fn test_partition_level() { let context_id = worker_node.id; hummock_manager - .register_table_ids(&[(100, 2)]) + .register_table_ids_for_test(&[(100, 2)]) .await .unwrap(); hummock_manager - .register_table_ids(&[(101, 2)]) + .register_table_ids_for_test(&[(101, 2)]) .await .unwrap(); let sst_1 = gen_extend_sstable_info(10, 2, 1, vec![100, 101]); hummock_manager - .commit_epoch( - 30, - CommitEpochInfo::for_test(vec![sst_1.clone()], HashMap::from([(10, context_id)])), - ) + .commit_epoch_for_test(30, vec![sst_1.clone()], HashMap::from([(10, context_id)])) .await .unwrap(); // Construct data via manual compaction @@ -2314,9 +2234,10 @@ async fn test_partition_level() { sst.sst_info.file_size = 10 * MB; sst.sst_info.uncompressed_file_size = 10 * MB; hummock_manager - .commit_epoch( + .commit_epoch_for_test( epoch, - CommitEpochInfo::for_test(vec![sst], HashMap::from([(global_sst_id, context_id)])), + vec![sst], + HashMap::from([(global_sst_id, context_id)]), ) .await .unwrap(); @@ -2354,3 +2275,136 @@ async fn test_partition_level() { } } } + +#[tokio::test] +async fn test_unregister_moved_table() { + let (_env, hummock_manager, _, worker_node) = setup_compute_env(80).await; + let context_id = worker_node.id; + let original_groups = hummock_manager + .get_current_version() + .await + .levels + .keys() + .cloned() + .sorted() + .collect_vec(); + assert_eq!( + original_groups, + vec![ + StaticCompactionGroupId::StateDefault as u64, + StaticCompactionGroupId::MaterializedView as u64 + ] + ); + + hummock_manager + .register_table_ids_for_test(&[(100, 2)]) + .await + .unwrap(); + hummock_manager + .register_table_ids_for_test(&[(101, 2)]) + .await + .unwrap(); + let sst_1 = ExtendedSstableInfo { + compaction_group_id: 2, + sst_info: SstableInfo { + object_id: 10, + sst_id: 10, + key_range: Some(KeyRange { + left: iterator_test_key_of_epoch(100, 1, 20), + right: iterator_test_key_of_epoch(100, 100, 20), + right_exclusive: false, + }), + table_ids: vec![100], + min_epoch: 20, + max_epoch: 20, + ..Default::default() + }, + table_stats: Default::default(), + }; + let sst_2 = ExtendedSstableInfo { + compaction_group_id: 2, + sst_info: SstableInfo { + object_id: 11, + sst_id: 11, + key_range: Some(KeyRange { + left: iterator_test_key_of_epoch(100, 101, 20), + right: iterator_test_key_of_epoch(101, 100, 20), + right_exclusive: false, + }), + table_ids: vec![100, 101], + min_epoch: 20, + max_epoch: 20, + ..Default::default() + }, + table_stats: Default::default(), + }; + hummock_manager + .commit_epoch_for_test( + 30, + vec![sst_1, sst_2], + HashMap::from([(10, context_id), (11, context_id)]), + ) + .await + .unwrap(); + + let new_group_id = hummock_manager + .split_compaction_group(2, &[100]) + .await + .unwrap(); + assert_ne!(new_group_id, 2); + assert!(new_group_id > StaticCompactionGroupId::End as u64); + + let current_version = hummock_manager.get_current_version().await; + assert_eq!( + new_group_id, + current_version.levels.keys().max().cloned().unwrap() + ); + assert_eq!(current_version.levels.len(), 3); + assert_eq!( + get_compaction_group_object_ids(¤t_version, 2), + vec![10, 11] + ); + assert_eq!( + get_compaction_group_object_ids(¤t_version, new_group_id), + vec![10, 11] + ); + assert_eq!( + current_version + .state_table_info + .compaction_group_member_table_ids(2) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), + vec![101] + ); + assert_eq!( + current_version + .state_table_info + .compaction_group_member_table_ids(new_group_id) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), + vec![100] + ); + + hummock_manager + .unregister_table_ids([TableId::new(100)]) + .await + .unwrap(); + let current_version = hummock_manager.get_current_version().await; + assert_eq!(current_version.levels.len(), 2); + assert!(!current_version.levels.contains_key(&new_group_id)); + assert_eq!( + get_compaction_group_object_ids(¤t_version, 2), + vec![10, 11] + ); + assert_eq!( + current_version + .state_table_info + .compaction_group_member_table_ids(2) + .iter() + .map(|table_id| table_id.table_id) + .collect_vec(), + vec![101] + ); +} diff --git a/src/meta/src/hummock/manager/timer_task.rs b/src/meta/src/hummock/manager/timer_task.rs new file mode 100644 index 0000000000000..bb4a9fa86b06c --- /dev/null +++ b/src/meta/src/hummock/manager/timer_task.rs @@ -0,0 +1,494 @@ +// Copyright 2024 RisingWave Labs +// +// 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::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::Duration; + +use futures::future::Either; +use futures::stream::BoxStream; +use futures::{FutureExt, StreamExt}; +use itertools::Itertools; +use risingwave_common::system_param::reader::SystemParamsRead; +use risingwave_hummock_sdk::compaction_group::hummock_version_ext::get_compaction_group_ids; +use risingwave_pb::hummock::compact_task::{self, TaskStatus}; +use risingwave_pb::hummock::level_handler::RunningCompactTask; +use rw_futures_util::select_all; +use thiserror_ext::AsReport; +use tokio::sync::oneshot::Sender; +use tokio::task::JoinHandle; +use tokio_stream::wrappers::IntervalStream; +use tracing::warn; + +use crate::hummock::metrics_utils::{trigger_lsm_stat, trigger_mv_stat}; +use crate::hummock::{HummockManager, TASK_NORMAL}; + +impl HummockManager { + pub fn hummock_timer_task(hummock_manager: Arc) -> (JoinHandle<()>, Sender<()>) { + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + let join_handle = tokio::spawn(async move { + const CHECK_PENDING_TASK_PERIOD_SEC: u64 = 300; + const STAT_REPORT_PERIOD_SEC: u64 = 20; + const COMPACTION_HEARTBEAT_PERIOD_SEC: u64 = 1; + + pub enum HummockTimerEvent { + GroupSplit, + CheckDeadTask, + Report, + CompactionHeartBeatExpiredCheck, + + DynamicCompactionTrigger, + SpaceReclaimCompactionTrigger, + TtlCompactionTrigger, + TombstoneCompactionTrigger, + + FullGc, + } + let mut check_compact_trigger_interval = + tokio::time::interval(Duration::from_secs(CHECK_PENDING_TASK_PERIOD_SEC)); + check_compact_trigger_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + check_compact_trigger_interval.reset(); + + let check_compact_trigger = IntervalStream::new(check_compact_trigger_interval) + .map(|_| HummockTimerEvent::CheckDeadTask); + + let mut stat_report_interval = + tokio::time::interval(std::time::Duration::from_secs(STAT_REPORT_PERIOD_SEC)); + stat_report_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + stat_report_interval.reset(); + let stat_report_trigger = + IntervalStream::new(stat_report_interval).map(|_| HummockTimerEvent::Report); + + let mut compaction_heartbeat_interval = tokio::time::interval( + std::time::Duration::from_secs(COMPACTION_HEARTBEAT_PERIOD_SEC), + ); + compaction_heartbeat_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + compaction_heartbeat_interval.reset(); + let compaction_heartbeat_trigger = IntervalStream::new(compaction_heartbeat_interval) + .map(|_| HummockTimerEvent::CompactionHeartBeatExpiredCheck); + + let mut min_trigger_interval = tokio::time::interval(Duration::from_secs( + hummock_manager.env.opts.periodic_compaction_interval_sec, + )); + min_trigger_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + min_trigger_interval.reset(); + let dynamic_tick_trigger = IntervalStream::new(min_trigger_interval) + .map(|_| HummockTimerEvent::DynamicCompactionTrigger); + + let mut min_space_reclaim_trigger_interval = + tokio::time::interval(Duration::from_secs( + hummock_manager + .env + .opts + .periodic_space_reclaim_compaction_interval_sec, + )); + min_space_reclaim_trigger_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + min_space_reclaim_trigger_interval.reset(); + let space_reclaim_trigger = IntervalStream::new(min_space_reclaim_trigger_interval) + .map(|_| HummockTimerEvent::SpaceReclaimCompactionTrigger); + + let mut min_ttl_reclaim_trigger_interval = tokio::time::interval(Duration::from_secs( + hummock_manager + .env + .opts + .periodic_ttl_reclaim_compaction_interval_sec, + )); + min_ttl_reclaim_trigger_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + min_ttl_reclaim_trigger_interval.reset(); + let ttl_reclaim_trigger = IntervalStream::new(min_ttl_reclaim_trigger_interval) + .map(|_| HummockTimerEvent::TtlCompactionTrigger); + + let mut full_gc_interval = tokio::time::interval(Duration::from_secs( + hummock_manager.env.opts.full_gc_interval_sec, + )); + full_gc_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + full_gc_interval.reset(); + let full_gc_trigger = + IntervalStream::new(full_gc_interval).map(|_| HummockTimerEvent::FullGc); + + let mut tombstone_reclaim_trigger_interval = + tokio::time::interval(Duration::from_secs( + hummock_manager + .env + .opts + .periodic_tombstone_reclaim_compaction_interval_sec, + )); + tombstone_reclaim_trigger_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + tombstone_reclaim_trigger_interval.reset(); + let tombstone_reclaim_trigger = IntervalStream::new(tombstone_reclaim_trigger_interval) + .map(|_| HummockTimerEvent::TombstoneCompactionTrigger); + + let mut triggers: Vec> = vec![ + Box::pin(check_compact_trigger), + Box::pin(stat_report_trigger), + Box::pin(compaction_heartbeat_trigger), + Box::pin(dynamic_tick_trigger), + Box::pin(space_reclaim_trigger), + Box::pin(ttl_reclaim_trigger), + Box::pin(full_gc_trigger), + Box::pin(tombstone_reclaim_trigger), + ]; + + let periodic_check_split_group_interval_sec = hummock_manager + .env + .opts + .periodic_split_compact_group_interval_sec; + + if periodic_check_split_group_interval_sec > 0 { + let mut split_group_trigger_interval = tokio::time::interval(Duration::from_secs( + periodic_check_split_group_interval_sec, + )); + split_group_trigger_interval + .set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + + let split_group_trigger = IntervalStream::new(split_group_trigger_interval) + .map(|_| HummockTimerEvent::GroupSplit); + triggers.push(Box::pin(split_group_trigger)); + } + + let event_stream = select_all(triggers); + use futures::pin_mut; + pin_mut!(event_stream); + + let shutdown_rx_shared = shutdown_rx.shared(); + + tracing::info!( + "Hummock timer task tracing [GroupSplit interval {} sec] [CheckDeadTask interval {} sec] [Report interval {} sec] [CompactionHeartBeat interval {} sec]", + periodic_check_split_group_interval_sec, CHECK_PENDING_TASK_PERIOD_SEC, STAT_REPORT_PERIOD_SEC, COMPACTION_HEARTBEAT_PERIOD_SEC + ); + + loop { + let item = + futures::future::select(event_stream.next(), shutdown_rx_shared.clone()).await; + + match item { + Either::Left((event, _)) => { + if let Some(event) = event { + match event { + HummockTimerEvent::CheckDeadTask => { + if hummock_manager.env.opts.compaction_deterministic_test { + continue; + } + + hummock_manager.check_dead_task().await; + } + + HummockTimerEvent::GroupSplit => { + if hummock_manager.env.opts.compaction_deterministic_test { + continue; + } + + hummock_manager.on_handle_check_split_multi_group().await; + } + + HummockTimerEvent::Report => { + let (current_version, id_to_config, version_stats) = { + let versioning_guard = + hummock_manager.versioning.read().await; + + let configs = + hummock_manager.get_compaction_group_map().await; + let versioning_deref = versioning_guard; + ( + versioning_deref.current_version.clone(), + configs, + versioning_deref.version_stats.clone(), + ) + }; + + if let Some(mv_id_to_all_table_ids) = hummock_manager + .metadata_manager + .get_job_id_to_internal_table_ids_mapping() + .await + { + trigger_mv_stat( + &hummock_manager.metrics, + &version_stats, + mv_id_to_all_table_ids, + ); + } + + for compaction_group_id in + get_compaction_group_ids(¤t_version) + { + let compaction_group_config = + &id_to_config[&compaction_group_id]; + + let group_levels = current_version + .get_compaction_group_levels( + compaction_group_config.group_id(), + ); + + trigger_lsm_stat( + &hummock_manager.metrics, + compaction_group_config.compaction_config(), + group_levels, + compaction_group_config.group_id(), + ) + } + } + + HummockTimerEvent::CompactionHeartBeatExpiredCheck => { + let compactor_manager = + hummock_manager.compactor_manager.clone(); + + // TODO: add metrics to track expired tasks + // The cancel task has two paths + // 1. compactor heartbeat cancels the expired task based on task + // progress (meta + compactor) + // 2. meta periodically scans the task and performs a cancel on + // the meta side for tasks that are not updated by heartbeat + let expired_tasks: Vec = compactor_manager + .get_heartbeat_expired_tasks() + .into_iter() + .map(|task| task.task_id) + .collect(); + if !expired_tasks.is_empty() { + tracing::info!( + expired_tasks = ?expired_tasks, + "Heartbeat expired compaction tasks detected. Attempting to cancel tasks.", + ); + if let Err(e) = hummock_manager + .cancel_compact_tasks( + expired_tasks.clone(), + TaskStatus::HeartbeatCanceled, + ) + .await + { + tracing::error!( + expired_tasks = ?expired_tasks, + error = %e.as_report(), + "Attempt to remove compaction task due to elapsed heartbeat failed. We will continue to track its heartbeat + until we can successfully report its status", + ); + } + } + } + + HummockTimerEvent::DynamicCompactionTrigger => { + // Disable periodic trigger for compaction_deterministic_test. + if hummock_manager.env.opts.compaction_deterministic_test { + continue; + } + + hummock_manager + .on_handle_trigger_multi_group( + compact_task::TaskType::Dynamic, + ) + .await; + } + + HummockTimerEvent::SpaceReclaimCompactionTrigger => { + // Disable periodic trigger for compaction_deterministic_test. + if hummock_manager.env.opts.compaction_deterministic_test { + continue; + } + + hummock_manager + .on_handle_trigger_multi_group( + compact_task::TaskType::SpaceReclaim, + ) + .await; + } + + HummockTimerEvent::TtlCompactionTrigger => { + // Disable periodic trigger for compaction_deterministic_test. + if hummock_manager.env.opts.compaction_deterministic_test { + continue; + } + + hummock_manager + .on_handle_trigger_multi_group(compact_task::TaskType::Ttl) + .await; + } + + HummockTimerEvent::TombstoneCompactionTrigger => { + // Disable periodic trigger for compaction_deterministic_test. + if hummock_manager.env.opts.compaction_deterministic_test { + continue; + } + + hummock_manager + .on_handle_trigger_multi_group( + compact_task::TaskType::Tombstone, + ) + .await; + } + + HummockTimerEvent::FullGc => { + if hummock_manager + .start_full_gc(Duration::from_secs(3600)) + .is_ok() + { + tracing::info!("Start full GC from meta node."); + } + } + } + } + } + + Either::Right((_, _shutdown)) => { + tracing::info!("Hummock timer loop is stopped"); + break; + } + } + } + }); + (join_handle, shutdown_tx) + } +} + +impl HummockManager { + async fn check_dead_task(&self) { + const MAX_COMPACTION_L0_MULTIPLIER: u64 = 32; + const MAX_COMPACTION_DURATION_SEC: u64 = 20 * 60; + let (groups, configs) = { + let versioning_guard = self.versioning.read().await; + let g = versioning_guard + .current_version + .levels + .iter() + .map(|(id, group)| { + ( + *id, + group + .l0 + .as_ref() + .unwrap() + .sub_levels + .iter() + .map(|level| level.total_file_size) + .sum::(), + ) + }) + .collect_vec(); + let c = self.get_compaction_group_map().await; + (g, c) + }; + let mut slowdown_groups: HashMap = HashMap::default(); + { + for (group_id, l0_file_size) in groups { + let group = &configs[&group_id]; + if l0_file_size + > MAX_COMPACTION_L0_MULTIPLIER + * group.compaction_config.max_bytes_for_level_base + { + slowdown_groups.insert(group_id, l0_file_size); + } + } + } + if slowdown_groups.is_empty() { + return; + } + let mut pending_tasks: HashMap = HashMap::default(); + { + let compaction_guard = self.compaction.read().await; + for group_id in slowdown_groups.keys() { + if let Some(status) = compaction_guard.compaction_statuses.get(group_id) { + for (idx, level_handler) in status.level_handlers.iter().enumerate() { + let tasks = level_handler.get_pending_tasks().to_vec(); + if tasks.is_empty() { + continue; + } + for task in tasks { + pending_tasks.insert(task.task_id, (*group_id, idx, task)); + } + } + } + } + } + let task_ids = pending_tasks.keys().cloned().collect_vec(); + let task_infos = self + .compactor_manager + .check_tasks_status(&task_ids, Duration::from_secs(MAX_COMPACTION_DURATION_SEC)); + for (task_id, (compact_time, status)) in task_infos { + if status == TASK_NORMAL { + continue; + } + if let Some((group_id, level_id, task)) = pending_tasks.get(&task_id) { + let group_size = *slowdown_groups.get(group_id).unwrap(); + warn!("COMPACTION SLOW: the task-{} of group-{}(size: {}MB) level-{} has not finished after {:?}, {}, it may cause pending sstable files({:?}) blocking other task.", + task_id, *group_id,group_size / 1024 / 1024,*level_id, compact_time, status, task.ssts); + } + } + } + + /// * For compaction group with only one single state-table, do not change it again. + /// * For state-table which only write less than `HISTORY_TABLE_INFO_WINDOW_SIZE` times, do not + /// change it. Because we need more statistic data to decide split strategy. + /// * For state-table with low throughput which write no more than + /// `min_table_split_write_throughput` data, never split it. + /// * For state-table whose size less than `min_table_split_size`, do not split it unless its + /// throughput keep larger than `table_write_throughput_threshold` for a long time. + /// * For state-table whose throughput less than `min_table_split_write_throughput`, do not + /// increase it size of base-level. + async fn on_handle_check_split_multi_group(&self) { + let params = self.env.system_params_reader().await; + let barrier_interval_ms = params.barrier_interval_ms() as u64; + let checkpoint_secs = std::cmp::max( + 1, + params.checkpoint_frequency() * barrier_interval_ms / 1000, + ); + let created_tables = match self.metadata_manager.get_created_table_ids().await { + Ok(created_tables) => created_tables, + Err(err) => { + tracing::warn!(error = %err.as_report(), "failed to fetch created table ids"); + return; + } + }; + let created_tables: HashSet = HashSet::from_iter(created_tables); + let table_write_throughput = self.history_table_throughput.read().clone(); + let mut group_infos = self.calculate_compaction_group_statistic().await; + group_infos.sort_by_key(|group| group.group_size); + group_infos.reverse(); + + for group in &group_infos { + if group.table_statistic.len() == 1 { + // no need to handle the separate compaciton group + continue; + } + + for (table_id, table_size) in &group.table_statistic { + self.try_move_table_to_dedicated_cg( + &table_write_throughput, + table_id, + table_size, + !created_tables.contains(table_id), + checkpoint_secs, + group.group_id, + group.group_size, + ) + .await; + } + } + } + + async fn on_handle_trigger_multi_group(&self, task_type: compact_task::TaskType) { + for cg_id in self.compaction_group_ids().await { + if let Err(e) = self.compaction_state.try_sched_compaction(cg_id, task_type) { + tracing::warn!( + error = %e.as_report(), + "Failed to schedule {:?} compaction for compaction group {}", + task_type, + cg_id, + ); + } + } + } +} diff --git a/src/meta/src/hummock/manager/transaction.rs b/src/meta/src/hummock/manager/transaction.rs new file mode 100644 index 0000000000000..c467e95adfdbe --- /dev/null +++ b/src/meta/src/hummock/manager/transaction.rs @@ -0,0 +1,245 @@ +// Copyright 2024 RisingWave Labs +// +// 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::BTreeMap; +use std::ops::{Deref, DerefMut}; + +use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta}; +use risingwave_hummock_sdk::HummockVersionId; +use risingwave_pb::hummock::HummockVersionStats; +use risingwave_pb::meta::subscribe_response::{Info, Operation}; + +use crate::manager::NotificationManager; +use crate::model::{ + InMemValTransaction, MetadataModelResult, Transactional, ValTransaction, VarTransaction, +}; +use crate::rpc::metrics::MetaMetrics; + +fn trigger_delta_log_stats(metrics: &MetaMetrics, total_number: usize) { + metrics.delta_log_count.set(total_number as _); +} + +fn trigger_version_stat(metrics: &MetaMetrics, current_version: &HummockVersion) { + metrics + .max_committed_epoch + .set(current_version.max_committed_epoch as i64); + metrics + .version_size + .set(current_version.estimated_encode_len() as i64); + metrics + .safe_epoch + .set(current_version.visible_table_safe_epoch() as i64); + metrics.current_version_id.set(current_version.id as i64); +} + +pub(super) struct HummockVersionTransaction<'a> { + orig_version: &'a mut HummockVersion, + orig_deltas: &'a mut BTreeMap, + notification_manager: &'a NotificationManager, + meta_metrics: &'a MetaMetrics, + + pre_applied_version: Option<(HummockVersion, Vec)>, + disable_apply_to_txn: bool, +} + +impl<'a> HummockVersionTransaction<'a> { + pub(super) fn new( + version: &'a mut HummockVersion, + deltas: &'a mut BTreeMap, + notification_manager: &'a NotificationManager, + meta_metrics: &'a MetaMetrics, + ) -> Self { + Self { + orig_version: version, + orig_deltas: deltas, + pre_applied_version: None, + disable_apply_to_txn: false, + notification_manager, + meta_metrics, + } + } + + pub(super) fn disable_apply_to_txn(&mut self) { + assert!( + self.pre_applied_version.is_none(), + "should only call disable at the beginning of txn" + ); + self.disable_apply_to_txn = true; + } + + pub(super) fn latest_version(&self) -> &HummockVersion { + if let Some((version, _)) = &self.pre_applied_version { + version + } else { + self.orig_version + } + } + + pub(super) fn new_delta<'b>(&'b mut self) -> SingleDeltaTransaction<'a, 'b> { + let delta = self.latest_version().version_delta_after(); + SingleDeltaTransaction { + version_txn: self, + delta: Some(delta), + } + } + + fn pre_apply(&mut self, delta: HummockVersionDelta) { + let (version, deltas) = self + .pre_applied_version + .get_or_insert_with(|| (self.orig_version.clone(), Vec::with_capacity(1))); + version.apply_version_delta(&delta); + deltas.push(delta); + } +} + +impl<'a> InMemValTransaction for HummockVersionTransaction<'a> { + fn commit(self) { + if let Some((version, deltas)) = self.pre_applied_version { + *self.orig_version = version; + if !self.disable_apply_to_txn { + let pb_deltas = deltas.iter().map(|delta| delta.to_protobuf()).collect(); + self.notification_manager.notify_hummock_without_version( + Operation::Add, + Info::HummockVersionDeltas(risingwave_pb::hummock::HummockVersionDeltas { + version_deltas: pb_deltas, + }), + ); + } + for delta in deltas { + assert!(self.orig_deltas.insert(delta.id, delta.clone()).is_none()); + } + + trigger_delta_log_stats(self.meta_metrics, self.orig_deltas.len()); + trigger_version_stat(self.meta_metrics, self.orig_version); + } + } +} + +impl<'a, TXN> ValTransaction for HummockVersionTransaction<'a> +where + HummockVersionDelta: Transactional, + HummockVersionStats: Transactional, +{ + async fn apply_to_txn(&self, txn: &mut TXN) -> MetadataModelResult<()> { + if self.disable_apply_to_txn { + return Ok(()); + } + for delta in self + .pre_applied_version + .iter() + .flat_map(|(_, deltas)| deltas.iter()) + { + delta.upsert_in_transaction(txn).await?; + } + Ok(()) + } +} + +pub(super) struct SingleDeltaTransaction<'a, 'b> { + version_txn: &'b mut HummockVersionTransaction<'a>, + delta: Option, +} + +impl<'a, 'b> SingleDeltaTransaction<'a, 'b> { + pub(super) fn latest_version(&self) -> &HummockVersion { + self.version_txn.latest_version() + } + + pub(super) fn pre_apply(mut self) { + self.version_txn.pre_apply(self.delta.take().unwrap()); + } + + pub(super) fn with_latest_version( + &mut self, + f: impl FnOnce(&HummockVersion, &mut HummockVersionDelta), + ) { + f( + self.version_txn.latest_version(), + self.delta.as_mut().expect("should exist"), + ) + } +} + +impl<'a, 'b> Deref for SingleDeltaTransaction<'a, 'b> { + type Target = HummockVersionDelta; + + fn deref(&self) -> &Self::Target { + self.delta.as_ref().expect("should exist") + } +} + +impl<'a, 'b> DerefMut for SingleDeltaTransaction<'a, 'b> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.delta.as_mut().expect("should exist") + } +} + +impl<'a, 'b> Drop for SingleDeltaTransaction<'a, 'b> { + fn drop(&mut self) { + if let Some(delta) = self.delta.take() { + self.version_txn.pre_apply(delta); + } + } +} + +pub(super) struct HummockVersionStatsTransaction<'a> { + stats: VarTransaction<'a, HummockVersionStats>, + notification_manager: &'a NotificationManager, +} + +impl<'a> HummockVersionStatsTransaction<'a> { + pub(super) fn new( + stats: &'a mut HummockVersionStats, + notification_manager: &'a NotificationManager, + ) -> Self { + Self { + stats: VarTransaction::new(stats), + notification_manager, + } + } +} + +impl<'a> InMemValTransaction for HummockVersionStatsTransaction<'a> { + fn commit(self) { + if self.stats.has_new_value() { + let stats = self.stats.clone(); + self.stats.commit(); + self.notification_manager + .notify_frontend_without_version(Operation::Update, Info::HummockStats(stats)); + } + } +} + +impl<'a, TXN> ValTransaction for HummockVersionStatsTransaction<'a> +where + HummockVersionStats: Transactional, +{ + async fn apply_to_txn(&self, txn: &mut TXN) -> MetadataModelResult<()> { + self.stats.apply_to_txn(txn).await + } +} + +impl<'a> Deref for HummockVersionStatsTransaction<'a> { + type Target = HummockVersionStats; + + fn deref(&self) -> &Self::Target { + self.stats.deref() + } +} + +impl<'a> DerefMut for HummockVersionStatsTransaction<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.stats.deref_mut() + } +} diff --git a/src/meta/src/hummock/manager/utils.rs b/src/meta/src/hummock/manager/utils.rs index 45d75d4774e90..3d8cb04546284 100644 --- a/src/meta/src/hummock/manager/utils.rs +++ b/src/meta/src/hummock/manager/utils.rs @@ -19,16 +19,18 @@ macro_rules! commit_multi_var { ($meta_store:expr, $($val_txn:expr),*) => { { async { + use crate::model::{InMemValTransaction, ValTransaction}; match &$meta_store { - crate::manager::MetaStoreImpl::Kv(meta_store) => { + $crate::manager::MetaStoreImpl::Kv(meta_store) => { use crate::storage::Transaction; + use crate::storage::meta_store::MetaStore; let mut trx = Transaction::default(); $( - $val_txn.as_v1_ref().apply_to_txn(&mut trx).await?; + $val_txn.apply_to_txn(&mut trx).await?; )* meta_store.txn(trx).await?; $( - $val_txn.into_v1().commit(); + $val_txn.commit(); )* Result::Ok(()) } @@ -37,11 +39,11 @@ macro_rules! commit_multi_var { use crate::model::MetadataModelError; let mut trx = sql_meta_store.conn.begin().await.map_err(MetadataModelError::from)?; $( - $val_txn.as_v2_ref().apply_to_txn(&mut trx).await?; + $val_txn.apply_to_txn(&mut trx).await?; )* trx.commit().await.map_err(MetadataModelError::from)?; $( - $val_txn.into_v2().commit(); + $val_txn.commit(); )* Result::Ok(()) } @@ -51,14 +53,70 @@ macro_rules! commit_multi_var { }; } pub(crate) use commit_multi_var; +use risingwave_hummock_sdk::SstObjectIdRange; -macro_rules! create_trx_wrapper { - ($meta_store:expr, $wrapper:ident, $inner:expr) => {{ - match &$meta_store { - crate::manager::MetaStoreImpl::Kv(_) => $wrapper::V1($inner), - crate::manager::MetaStoreImpl::Sql(_) => $wrapper::V2($inner), - } - }}; -} +use crate::hummock::error::Result; +use crate::hummock::sequence::next_sstable_object_id; +use crate::hummock::HummockManager; -pub(crate) use create_trx_wrapper; +impl HummockManager { + #[cfg(test)] + pub(super) async fn check_state_consistency(&self) { + use crate::hummock::manager::compaction::Compaction; + use crate::hummock::manager::context::ContextInfo; + use crate::hummock::manager::versioning::Versioning; + let mut compaction_guard = self.compaction.write().await; + let mut versioning_guard = self.versioning.write().await; + let mut context_info_guard = self.context_info.write().await; + let objects_to_delete = self.delete_object_tracker.current(); + // We don't check `checkpoint` because it's allowed to update its in memory state without + // persisting to object store. + let get_state = |compaction_guard: &mut Compaction, + versioning_guard: &mut Versioning, + context_info_guard: &mut ContextInfo| { + let compact_statuses_copy = compaction_guard.compaction_statuses.clone(); + let compact_task_assignment_copy = compaction_guard.compact_task_assignment.clone(); + let pinned_versions_copy = context_info_guard.pinned_versions.clone(); + let pinned_snapshots_copy = context_info_guard.pinned_snapshots.clone(); + let hummock_version_deltas_copy = versioning_guard.hummock_version_deltas.clone(); + let version_stats_copy = versioning_guard.version_stats.clone(); + (( + compact_statuses_copy, + compact_task_assignment_copy, + pinned_versions_copy, + pinned_snapshots_copy, + hummock_version_deltas_copy, + version_stats_copy, + ),) + }; + let mem_state = get_state( + &mut compaction_guard, + &mut versioning_guard, + &mut context_info_guard, + ); + self.load_meta_store_state_impl( + &mut compaction_guard, + &mut versioning_guard, + &mut context_info_guard, + ) + .await + .expect("Failed to load state from meta store"); + let loaded_state = get_state( + &mut compaction_guard, + &mut versioning_guard, + &mut context_info_guard, + ); + assert_eq!( + mem_state, loaded_state, + "hummock in-mem state is inconsistent with meta store state", + ); + self.delete_object_tracker.clear(); + self.delete_object_tracker + .add(objects_to_delete.into_iter()); + } + + pub async fn get_new_sst_ids(&self, number: u32) -> Result { + let start_id = next_sstable_object_id(&self.env, number).await?; + Ok(SstObjectIdRange::new(start_id, start_id + number as u64)) + } +} diff --git a/src/meta/src/hummock/manager/versioning.rs b/src/meta/src/hummock/manager/versioning.rs index feeacc98b558a..2e6b2512a8be0 100644 --- a/src/meta/src/hummock/manager/versioning.rs +++ b/src/meta/src/hummock/manager/versioning.rs @@ -15,92 +15,56 @@ use std::cmp; use std::collections::{BTreeMap, HashMap, HashSet}; -use function_name::named; use itertools::Itertools; -use risingwave_common::util::epoch::INVALID_EPOCH; +use risingwave_common::catalog::TableId; use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - build_initial_compaction_group_levels, get_compaction_group_ids, BranchedSstInfo, + get_compaction_group_ids, get_table_compaction_group_id_mapping, BranchedSstInfo, }; -use risingwave_hummock_sdk::compaction_group::{StateTableId, StaticCompactionGroupId}; +use risingwave_hummock_sdk::compaction_group::StateTableId; use risingwave_hummock_sdk::table_stats::add_prost_table_stats_map; use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta}; use risingwave_hummock_sdk::{ - CompactionGroupId, HummockContextId, HummockSstableObjectId, HummockVersionId, FIRST_VERSION_ID, + CompactionGroupId, HummockContextId, HummockEpoch, HummockSstableObjectId, HummockVersionId, }; use risingwave_pb::common::WorkerNode; use risingwave_pb::hummock::write_limits::WriteLimit; use risingwave_pb::hummock::{ - CompactionConfig, HummockPinnedSnapshot, HummockPinnedVersion, HummockVersionStats, - SstableInfo, TableStats, + HummockPinnedSnapshot, HummockPinnedVersion, HummockSnapshot, HummockVersionStats, SstableInfo, + TableStats, }; use risingwave_pb::meta::subscribe_response::{Info, Operation}; use super::check_cg_write_limit; use crate::hummock::error::Result; use crate::hummock::manager::checkpoint::HummockVersionCheckpoint; -use crate::hummock::manager::worker::{HummockManagerEvent, HummockManagerEventSender}; -use crate::hummock::manager::{commit_multi_var, create_trx_wrapper, read_lock, write_lock}; -use crate::hummock::metrics_utils::{ - trigger_safepoint_stat, trigger_write_stop_stats, LocalTableMetrics, -}; +use crate::hummock::manager::commit_multi_var; +use crate::hummock::manager::context::ContextInfo; +use crate::hummock::manager::gc::DeleteObjectTracker; +use crate::hummock::manager::transaction::HummockVersionTransaction; +use crate::hummock::metrics_utils::{trigger_write_stop_stats, LocalTableMetrics}; use crate::hummock::model::CompactionGroup; use crate::hummock::HummockManager; -use crate::model::{VarTransaction, VarTransactionWrapper}; -use crate::storage::MetaStore; +use crate::model::VarTransaction; use crate::MetaResult; -/// `HummockVersionSafePoint` prevents hummock versions GE than it from being GC. -/// It's used by meta node itself to temporarily pin versions. -pub struct HummockVersionSafePoint { - pub id: HummockVersionId, - event_sender: HummockManagerEventSender, -} - -impl Drop for HummockVersionSafePoint { - fn drop(&mut self) { - if self - .event_sender - .send(HummockManagerEvent::DropSafePoint(self.id)) - .is_err() - { - tracing::debug!("failed to drop hummock version safe point {}", self.id); - } - } -} - #[derive(Default)] pub struct Versioning { // Volatile states below - /// Avoide commit epoch epochs + /// Avoid commit epoch epochs /// Don't persist compaction version delta to meta store pub disable_commit_epochs: bool, /// Latest hummock version pub current_version: HummockVersion, - /// Objects that waits to be deleted from object store. It comes from either compaction, or - /// full GC (listing object store). - pub objects_to_delete: HashSet, - /// SST whose `object_id` != `sst_id` - pub branched_ssts: BTreeMap< - // SST object id - HummockSstableObjectId, - BranchedSstInfo, - >, - /// `version_safe_points` is similar to `pinned_versions` expect for being a transient state. - pub version_safe_points: Vec, - /// Tables that write limit is trigger for. - pub write_limit: HashMap, + pub local_metrics: HashMap, // Persistent states below pub hummock_version_deltas: BTreeMap, - pub pinned_versions: BTreeMap, - pub pinned_snapshots: BTreeMap, /// Stats for latest hummock version. pub version_stats: HummockVersionStats, pub checkpoint: HummockVersionCheckpoint, - pub local_metrics: HashMap, } -impl Versioning { +impl ContextInfo { pub fn min_pinned_version_id(&self) -> HummockVersionId { let mut min_pinned_version_id = HummockVersionId::MAX; for id in self @@ -113,64 +77,30 @@ impl Versioning { } min_pinned_version_id } +} +impl Versioning { /// Marks all objects <= `min_pinned_version_id` for deletion. - pub(super) fn mark_objects_for_deletion(&mut self) { - let min_pinned_version_id = self.min_pinned_version_id(); - self.objects_to_delete.extend( + pub(super) fn mark_objects_for_deletion( + &self, + context_info: &ContextInfo, + delete_object_tracker: &DeleteObjectTracker, + ) { + let min_pinned_version_id = context_info.min_pinned_version_id(); + delete_object_tracker.add( self.checkpoint .stale_objects .iter() .filter(|(version_id, _)| **version_id <= min_pinned_version_id) - .flat_map(|(_, stale_objects)| stale_objects.id.clone()), + .flat_map(|(_, stale_objects)| stale_objects.id.iter().cloned()), ); } - - /// If there is some sst in the target group which is just split but we have not compact it, we - /// can not split or move state-table to those group, because it may cause data overlap. - pub fn check_branched_sst_in_target_group( - &self, - table_ids: &[StateTableId], - source_group_id: &CompactionGroupId, - target_group_id: &CompactionGroupId, - ) -> bool { - for groups in self.branched_ssts.values() { - if groups.contains_key(target_group_id) && groups.contains_key(source_group_id) { - return false; - } - } - let mut found_sstable_repeated = false; - let moving_table_ids: HashSet<&u32> = HashSet::from_iter(table_ids); - if let Some(group) = self.current_version.levels.get(target_group_id) { - let target_member_table_ids: HashSet = - HashSet::from_iter(group.member_table_ids.clone()); - self.current_version.level_iter(*source_group_id, |level| { - for sst in &level.table_infos { - if sst - .table_ids - .iter() - .all(|table_id| !moving_table_ids.contains(table_id)) - { - continue; - } - for table_id in &sst.table_ids { - if target_member_table_ids.contains(table_id) { - found_sstable_repeated = true; - return false; - } - } - } - true - }); - } - !found_sstable_repeated - } } impl HummockManager { - #[named] pub async fn list_pinned_version(&self) -> Vec { - read_lock!(self, versioning) + self.context_info + .read() .await .pinned_versions .values() @@ -178,9 +108,9 @@ impl HummockManager { .collect_vec() } - #[named] pub async fn list_pinned_snapshot(&self) -> Vec { - read_lock!(self, versioning) + self.context_info + .read() .await .pinned_snapshots .values() @@ -205,71 +135,90 @@ impl HummockManager { Ok(workers) } - #[named] - pub async fn get_version_stats(&self) -> HummockVersionStats { - read_lock!(self, versioning).await.version_stats.clone() + /// Gets current version without pinning it. + /// Should not be called inside [`HummockManager`], because it requests locks internally. + /// + /// Note: this method can hurt performance because it will clone a large object. + pub async fn get_current_version(&self) -> HummockVersion { + self.versioning.read().await.current_version.clone() } - #[named] - pub async fn register_safe_point(&self) -> HummockVersionSafePoint { - let mut wl = write_lock!(self, versioning).await; - let safe_point = HummockVersionSafePoint { - id: wl.current_version.id, - event_sender: self.event_sender.clone(), - }; - wl.version_safe_points.push(safe_point.id); - trigger_safepoint_stat(&self.metrics, &wl.version_safe_points); - safe_point + pub async fn get_current_max_committed_epoch(&self) -> HummockEpoch { + self.versioning + .read() + .await + .current_version + .max_committed_epoch } - #[named] - pub async fn unregister_safe_point(&self, safe_point: HummockVersionId) { - let mut wl = write_lock!(self, versioning).await; - let version_safe_points = &mut wl.version_safe_points; - if let Some(pos) = version_safe_points.iter().position(|sp| *sp == safe_point) { - version_safe_points.remove(pos); - } - trigger_safepoint_stat(&self.metrics, &wl.version_safe_points); + /// Gets the mapping from table id to compaction group id + pub async fn get_table_compaction_group_id_mapping( + &self, + ) -> HashMap { + get_table_compaction_group_id_mapping(&self.versioning.read().await.current_version) + } + + /// Get version deltas from meta store + #[cfg_attr(coverage, coverage(off))] + pub async fn list_version_deltas( + &self, + start_id: u64, + num_limit: u32, + committed_epoch_limit: HummockEpoch, + ) -> Result> { + let versioning = self.versioning.read().await; + let version_deltas = versioning + .hummock_version_deltas + .range(start_id..) + .map(|(_id, delta)| delta) + .filter(|delta| delta.max_committed_epoch <= committed_epoch_limit) + .take(num_limit as _) + .cloned() + .collect(); + Ok(version_deltas) + } + + pub async fn get_version_stats(&self) -> HummockVersionStats { + self.versioning.read().await.version_stats.clone() } /// Updates write limits for `target_groups` and sends notification. /// Returns true if `write_limit` has been modified. /// The implementation acquires `versioning` lock and `compaction_group_manager` lock. - #[named] pub(super) async fn try_update_write_limits( &self, target_group_ids: &[CompactionGroupId], ) -> bool { - let mut guard = write_lock!(self, versioning).await; - let config_mgr = self.compaction_group_manager.read().await; + let versioning = self.versioning.read().await; + let mut cg_manager = self.compaction_group_manager.write().await; let target_group_configs = target_group_ids .iter() .filter_map(|id| { - config_mgr + cg_manager .try_get_compaction_group_config(*id) .map(|config| (*id, config)) }) .collect(); let mut new_write_limits = calc_new_write_limits( target_group_configs, - guard.write_limit.clone(), - &guard.current_version, + cg_manager.write_limit.clone(), + &versioning.current_version, ); let all_group_ids: HashSet<_> = - HashSet::from_iter(get_compaction_group_ids(&guard.current_version)); + HashSet::from_iter(get_compaction_group_ids(&versioning.current_version)); new_write_limits.retain(|group_id, _| all_group_ids.contains(group_id)); - if new_write_limits == guard.write_limit { + if new_write_limits == cg_manager.write_limit { return false; } tracing::debug!("Hummock stopped write is updated: {:#?}", new_write_limits); trigger_write_stop_stats(&self.metrics, &new_write_limits); - guard.write_limit = new_write_limits; + cg_manager.write_limit = new_write_limits; self.env .notification_manager() .notify_hummock_without_version( Operation::Add, Info::HummockWriteLimits(risingwave_pb::hummock::WriteLimits { - write_limits: guard.write_limit.clone(), + write_limits: cg_manager.write_limit.clone(), }), ); true @@ -277,33 +226,88 @@ impl HummockManager { /// Gets write limits. /// The implementation acquires `versioning` lock. - #[named] pub async fn write_limits(&self) -> HashMap { - let guard = read_lock!(self, versioning).await; + let guard = self.compaction_group_manager.read().await; guard.write_limit.clone() } - #[named] pub async fn list_branched_objects(&self) -> BTreeMap { - let guard = read_lock!(self, versioning).await; - guard.branched_ssts.clone() + let guard = self.versioning.read().await; + guard.current_version.build_branched_sst_info() } - #[named] pub async fn rebuild_table_stats(&self) -> Result<()> { - use crate::model::ValTransaction; - let mut versioning = write_lock!(self, versioning).await; + let mut versioning = self.versioning.write().await; let new_stats = rebuild_table_stats(&versioning.current_version); - let mut version_stats = create_trx_wrapper!( - self.meta_store_ref(), - VarTransactionWrapper, - VarTransaction::new(&mut versioning.version_stats) - ); + let mut version_stats = VarTransaction::new(&mut versioning.version_stats); // version_stats.hummock_version_id is always 0 in meta store. version_stats.table_stats = new_stats.table_stats; commit_multi_var!(self.meta_store_ref(), version_stats)?; Ok(()) } + + pub async fn may_fill_backward_state_table_info(&self) -> Result<()> { + let mut versioning = self.versioning.write().await; + if versioning + .current_version + .need_fill_backward_compatible_state_table_info_delta() + { + let versioning: &mut Versioning = &mut versioning; + let mut version = HummockVersionTransaction::new( + &mut versioning.current_version, + &mut versioning.hummock_version_deltas, + self.env.notification_manager(), + &self.metrics, + ); + let mut new_version_delta = version.new_delta(); + new_version_delta.with_latest_version(|version, delta| { + version.may_fill_backward_compatible_state_table_info_delta(delta) + }); + new_version_delta.pre_apply(); + commit_multi_var!(self.meta_store_ref(), version)?; + } + Ok(()) + } + + pub fn latest_snapshot(&self) -> HummockSnapshot { + let snapshot = self.latest_snapshot.load(); + HummockSnapshot::clone(&snapshot) + } + + /// We don't commit an epoch without checkpoint. We will only update the `max_current_epoch`. + pub fn update_current_epoch(&self, max_current_epoch: HummockEpoch) -> HummockSnapshot { + // We only update `max_current_epoch`! + let prev_snapshot = self.latest_snapshot.rcu(|snapshot| HummockSnapshot { + committed_epoch: snapshot.committed_epoch, + current_epoch: max_current_epoch, + }); + assert!(prev_snapshot.current_epoch < max_current_epoch); + + tracing::trace!("new current epoch {}", max_current_epoch); + HummockSnapshot { + committed_epoch: prev_snapshot.committed_epoch, + current_epoch: max_current_epoch, + } + } + + pub async fn list_change_log_epochs( + &self, + table_id: u32, + min_epoch: u64, + max_count: u32, + ) -> Vec { + let versioning = self.versioning.read().await; + if let Some(table_change_log) = versioning + .current_version + .table_change_log + .get(&TableId::new(table_id)) + { + let table_change_log = table_change_log.clone(); + table_change_log.get_epochs(min_epoch, max_count as usize) + } else { + vec![] + } + } } /// Calculates write limits for `target_groups`. @@ -328,7 +332,12 @@ pub(super) fn calc_new_write_limits( new_write_limits.insert( *id, WriteLimit { - table_ids: levels.member_table_ids.clone(), + table_ids: version + .state_table_info + .compaction_group_member_table_ids(*id) + .iter() + .map(|table_id| table_id.table_id) + .collect(), reason: write_limit_type.as_str(), }, ); @@ -340,27 +349,6 @@ pub(super) fn calc_new_write_limits( new_write_limits } -pub(super) fn create_init_version(default_compaction_config: CompactionConfig) -> HummockVersion { - let mut init_version = HummockVersion { - id: FIRST_VERSION_ID, - levels: Default::default(), - max_committed_epoch: INVALID_EPOCH, - safe_epoch: INVALID_EPOCH, - table_watermarks: HashMap::new(), - table_change_log: HashMap::new(), - }; - for group_id in [ - StaticCompactionGroupId::StateDefault as CompactionGroupId, - StaticCompactionGroupId::MaterializedView as CompactionGroupId, - ] { - init_version.levels.insert( - group_id, - build_initial_compaction_group_levels(group_id, &default_compaction_config), - ); - } - init_version -} - /// Rebuilds table stats from the given version. /// Note that the result is approximate value. See `estimate_table_stats`. fn rebuild_table_stats(version: &HummockVersion) -> HummockVersionStats { @@ -416,29 +404,30 @@ mod tests { }; use crate::hummock::compaction::compaction_config::CompactionConfigBuilder; + use crate::hummock::manager::context::ContextInfo; use crate::hummock::manager::versioning::{ - calc_new_write_limits, estimate_table_stats, rebuild_table_stats, Versioning, + calc_new_write_limits, estimate_table_stats, rebuild_table_stats, }; use crate::hummock::model::CompactionGroup; #[test] fn test_min_pinned_version_id() { - let mut versioning = Versioning::default(); - assert_eq!(versioning.min_pinned_version_id(), HummockVersionId::MAX); - versioning.pinned_versions.insert( + let mut context_info = ContextInfo::default(); + assert_eq!(context_info.min_pinned_version_id(), HummockVersionId::MAX); + context_info.pinned_versions.insert( 1, HummockPinnedVersion { context_id: 1, min_pinned_id: 10, }, ); - assert_eq!(versioning.min_pinned_version_id(), 10); - versioning.version_safe_points.push(5); - assert_eq!(versioning.min_pinned_version_id(), 5); - versioning.version_safe_points.clear(); - assert_eq!(versioning.min_pinned_version_id(), 10); - versioning.pinned_versions.clear(); - assert_eq!(versioning.min_pinned_version_id(), HummockVersionId::MAX); + assert_eq!(context_info.min_pinned_version_id(), 10); + context_info.version_safe_points.push(5); + assert_eq!(context_info.min_pinned_version_id(), 5); + context_info.version_safe_points.clear(); + assert_eq!(context_info.min_pinned_version_id(), 10); + context_info.pinned_versions.clear(); + assert_eq!(context_info.min_pinned_version_id(), HummockVersionId::MAX); } #[test] @@ -564,14 +553,9 @@ mod tests { ); } - let mut version = HummockVersion { - id: 123, - levels: Default::default(), - max_committed_epoch: 0, - safe_epoch: 0, - table_watermarks: HashMap::new(), - table_change_log: HashMap::new(), - }; + let mut version = HummockVersion::default(); + version.id = 123; + for cg in 1..3 { version.levels.insert( cg, diff --git a/src/meta/src/hummock/metrics_utils.rs b/src/meta/src/hummock/metrics_utils.rs index be25c5cf452b2..3779ff5b2be97 100644 --- a/src/meta/src/hummock/metrics_utils.rs +++ b/src/meta/src/hummock/metrics_utils.rs @@ -20,14 +20,10 @@ use std::time::{SystemTime, UNIX_EPOCH}; use itertools::{enumerate, Itertools}; use prometheus::core::{AtomicU64, GenericCounter}; use prometheus::IntGauge; -use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - object_size_map, BranchedSstInfo, -}; +use risingwave_hummock_sdk::compaction_group::hummock_version_ext::object_size_map; use risingwave_hummock_sdk::table_stats::PbTableStatsMap; use risingwave_hummock_sdk::version::HummockVersion; -use risingwave_hummock_sdk::{ - CompactionGroupId, HummockContextId, HummockEpoch, HummockSstableObjectId, HummockVersionId, -}; +use risingwave_hummock_sdk::{CompactionGroupId, HummockContextId, HummockEpoch, HummockVersionId}; use risingwave_pb::hummock::hummock_version::Levels; use risingwave_pb::hummock::write_limits::WriteLimit; use risingwave_pb::hummock::{ @@ -113,17 +109,6 @@ pub fn trigger_local_table_stat( } } -pub fn trigger_version_stat(metrics: &MetaMetrics, current_version: &HummockVersion) { - metrics - .max_committed_epoch - .set(current_version.max_committed_epoch as i64); - metrics - .version_size - .set(current_version.estimated_encode_len() as i64); - metrics.safe_epoch.set(current_version.safe_epoch as i64); - metrics.current_version_id.set(current_version.id as i64); -} - pub fn trigger_mv_stat( metrics: &MetaMetrics, version_stats: &HummockVersionStats, @@ -431,16 +416,6 @@ pub fn trigger_pin_unpin_snapshot_state( } } -pub fn trigger_safepoint_stat(metrics: &MetaMetrics, safepoints: &[HummockVersionId]) { - if let Some(sp) = safepoints.iter().min() { - metrics.min_safepoint_version_id.set(*sp as _); - } else { - metrics - .min_safepoint_version_id - .set(HummockVersionId::MAX as _); - } -} - pub fn trigger_gc_stat( metrics: &MetaMetrics, checkpoint: &HummockVersionCheckpoint, @@ -478,10 +453,6 @@ pub fn trigger_gc_stat( metrics.stale_object_count.set(stale_object_count as _); } -pub fn trigger_delta_log_stats(metrics: &MetaMetrics, total_number: usize) { - metrics.delta_log_count.set(total_number as _); -} - // Triggers a report on compact_pending_bytes_needed pub fn trigger_lsm_stat( metrics: &MetaMetrics, @@ -555,36 +526,36 @@ pub fn trigger_write_stop_stats( } } -pub fn trigger_split_stat( - metrics: &MetaMetrics, - compaction_group_id: CompactionGroupId, - member_table_id_len: usize, - branched_ssts: &BTreeMap< - // SST object id - HummockSstableObjectId, - BranchedSstInfo, - >, -) { - let group_label = compaction_group_id.to_string(); - metrics - .state_table_count - .with_label_values(&[&group_label]) - .set(member_table_id_len as _); +pub fn trigger_split_stat(metrics: &MetaMetrics, version: &HummockVersion) { + let branched_ssts = version.build_branched_sst_info(); - let branched_sst_count: usize = branched_ssts - .values() - .map(|branched_map| { - branched_map - .keys() - .filter(|group_id| **group_id == compaction_group_id) - .count() - }) - .sum(); + for compaction_group_id in version.levels.keys() { + let group_label = compaction_group_id.to_string(); + metrics + .state_table_count + .with_label_values(&[&group_label]) + .set( + version + .state_table_info + .compaction_group_member_table_ids(*compaction_group_id) + .len() as _, + ); - metrics - .branched_sst_count - .with_label_values(&[&group_label]) - .set(branched_sst_count as _); + let branched_sst_count: usize = branched_ssts + .values() + .map(|branched_map| { + branched_map + .keys() + .filter(|group_id| *group_id == compaction_group_id) + .count() + }) + .sum(); + + metrics + .branched_sst_count + .with_label_values(&[&group_label]) + .set(branched_sst_count as _); + } } pub fn build_level_metrics_label(compaction_group_id: u64, level_idx: usize) -> String { diff --git a/src/meta/src/hummock/mock_hummock_meta_client.rs b/src/meta/src/hummock/mock_hummock_meta_client.rs index 9a888e27bc78e..dea226e28b047 100644 --- a/src/meta/src/hummock/mock_hummock_meta_client.rs +++ b/src/meta/src/hummock/mock_hummock_meta_client.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::BTreeMap; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use std::time::SystemTime; @@ -21,6 +22,7 @@ use async_trait::async_trait; use fail::fail_point; use futures::stream::BoxStream; use futures::{Stream, StreamExt}; +use risingwave_hummock_sdk::change_log::build_table_change_log_delta; use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId; use risingwave_hummock_sdk::version::HummockVersion; use risingwave_hummock_sdk::{ @@ -156,27 +158,43 @@ impl HummockMetaClient for MockHummockMetaClient { } async fn commit_epoch(&self, epoch: HummockEpoch, sync_result: SyncResult) -> Result<()> { + let version: HummockVersion = self.hummock_manager.get_current_version().await; let sst_to_worker = sync_result .uncommitted_ssts .iter() .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), self.context_id)) .collect(); let new_table_watermark = sync_result.table_watermarks; - + let table_change_log = build_table_change_log_delta( + sync_result + .old_value_ssts + .into_iter() + .map(|sst| sst.sst_info), + sync_result.uncommitted_ssts.iter().map(|sst| &sst.sst_info), + &vec![epoch], + version + .state_table_info + .info() + .keys() + .map(|table_id| (table_id.table_id, 0)), + ); self.hummock_manager - .commit_epoch( + .commit_epoch(CommitEpochInfo::new( + sync_result + .uncommitted_ssts + .into_iter() + .map(|sst| sst.into()) + .collect(), + new_table_watermark, + sst_to_worker, + None, + table_change_log, + BTreeMap::from_iter([( + epoch, + version.state_table_info.info().keys().cloned().collect(), + )]), epoch, - CommitEpochInfo::new( - sync_result - .uncommitted_ssts - .into_iter() - .map(|sst| sst.into()) - .collect(), - new_table_watermark, - sst_to_worker, - None, - ), - ) + )) .await .map_err(mock_err)?; Ok(()) diff --git a/src/meta/src/hummock/mod.rs b/src/meta/src/hummock/mod.rs index 65017943cb90f..4150f51280d3d 100644 --- a/src/meta/src/hummock/mod.rs +++ b/src/meta/src/hummock/mod.rs @@ -23,7 +23,6 @@ mod metrics_utils; #[cfg(any(test, feature = "test"))] pub mod mock_hummock_meta_client; pub mod model; -#[cfg(any(test, feature = "test"))] pub mod test_utils; mod utils; mod vacuum; diff --git a/src/meta/src/hummock/model/compact_task_assignment.rs b/src/meta/src/hummock/model/compact_task_assignment.rs index e8b9402680795..74fdf6e84dadd 100644 --- a/src/meta/src/hummock/model/compact_task_assignment.rs +++ b/src/meta/src/hummock/model/compact_task_assignment.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use prost::Message; use risingwave_hummock_sdk::HummockCompactionTaskId; use risingwave_pb::hummock::CompactTaskAssignment; @@ -32,10 +31,6 @@ impl MetadataModel for CompactTaskAssignment { self.clone() } - fn to_protobuf_encoded_vec(&self) -> Vec { - self.encode_to_vec() - } - fn from_protobuf(prost: Self::PbType) -> Self { prost } diff --git a/src/meta/src/hummock/model/pinned_snapshot.rs b/src/meta/src/hummock/model/pinned_snapshot.rs index c7db58719f46c..f485d9dab7211 100644 --- a/src/meta/src/hummock/model/pinned_snapshot.rs +++ b/src/meta/src/hummock/model/pinned_snapshot.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use prost::Message; use risingwave_hummock_sdk::HummockContextId; use risingwave_pb::hummock::HummockPinnedSnapshot; @@ -32,10 +31,6 @@ impl MetadataModel for HummockPinnedSnapshot { self.clone() } - fn to_protobuf_encoded_vec(&self) -> Vec { - self.encode_to_vec() - } - fn from_protobuf(prost: Self::PbType) -> Self { prost } diff --git a/src/meta/src/hummock/model/pinned_version.rs b/src/meta/src/hummock/model/pinned_version.rs index 1b92bc300c797..e8f6b2e65e75e 100644 --- a/src/meta/src/hummock/model/pinned_version.rs +++ b/src/meta/src/hummock/model/pinned_version.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use prost::Message; use risingwave_hummock_sdk::HummockContextId; use risingwave_pb::hummock::HummockPinnedVersion; @@ -32,10 +31,6 @@ impl MetadataModel for HummockPinnedVersion { self.clone() } - fn to_protobuf_encoded_vec(&self) -> Vec { - self.encode_to_vec() - } - fn from_protobuf(prost: Self::PbType) -> Self { prost } diff --git a/src/meta/src/hummock/model/version_delta.rs b/src/meta/src/hummock/model/version_delta.rs index 34cadc675da7a..1a87b9d456989 100644 --- a/src/meta/src/hummock/model/version_delta.rs +++ b/src/meta/src/hummock/model/version_delta.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use prost::Message; use risingwave_hummock_sdk::version::HummockVersionDelta; use risingwave_hummock_sdk::HummockVersionId; use risingwave_pb::hummock::PbHummockVersionDelta; @@ -33,10 +32,6 @@ impl MetadataModel for HummockVersionDelta { self.to_protobuf() } - fn to_protobuf_encoded_vec(&self) -> Vec { - self.to_protobuf().encode_to_vec() - } - fn from_protobuf(prost: Self::PbType) -> Self { Self::from_persisted_protobuf(&prost) } diff --git a/src/meta/src/hummock/model/version_stats.rs b/src/meta/src/hummock/model/version_stats.rs index e6ee772e3de9e..512adca422bd5 100644 --- a/src/meta/src/hummock/model/version_stats.rs +++ b/src/meta/src/hummock/model/version_stats.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use prost::Message; use risingwave_hummock_sdk::HummockVersionId; use risingwave_pb::hummock::HummockVersionStats; @@ -33,10 +32,6 @@ impl MetadataModel for HummockVersionStats { self.clone() } - fn to_protobuf_encoded_vec(&self) -> Vec { - self.encode_to_vec() - } - fn from_protobuf(prost: Self::PbType) -> Self { prost } diff --git a/src/meta/src/hummock/test_utils.rs b/src/meta/src/hummock/test_utils.rs index 23898f6965ca1..32cbd4932adf6 100644 --- a/src/meta/src/hummock/test_utils.rs +++ b/src/meta/src/hummock/test_utils.rs @@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg(any(test, feature = "test"))] + use std::sync::Arc; use std::time::Duration; use itertools::Itertools; +use risingwave_common::catalog::TableId; use risingwave_common::util::epoch::test_epoch; use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId; use risingwave_hummock_sdk::key::key_with_epoch; @@ -24,15 +27,13 @@ use risingwave_hummock_sdk::{ CompactionGroupId, HummockContextId, HummockEpoch, HummockSstableObjectId, LocalSstableInfo, }; use risingwave_pb::common::{HostAddress, WorkerNode, WorkerType}; -#[cfg(test)] use risingwave_pb::hummock::compact_task::TaskStatus; -use risingwave_pb::hummock::{CompactionConfig, HummockSnapshot, KeyRange, SstableInfo}; +use risingwave_pb::hummock::{CompactionConfig, KeyRange, SstableInfo}; use risingwave_pb::meta::add_worker_node_request::Property; use crate::hummock::compaction::compaction_config::CompactionConfigBuilder; -#[cfg(test)] use crate::hummock::compaction::selector::default_compaction_selector; -use crate::hummock::{CommitEpochInfo, CompactorManager, HummockManager, HummockManagerRef}; +use crate::hummock::{CompactorManager, HummockManager, HummockManagerRef}; use crate::manager::{ ClusterManager, ClusterManagerRef, FragmentManager, MetaSrvEnv, META_NODE_ID, }; @@ -49,7 +50,6 @@ pub fn to_local_sstable_info(ssts: &[SstableInfo]) -> Vec { .collect_vec() } -#[cfg(test)] pub async fn add_test_tables( hummock_manager: &HummockManager, context_id: HummockContextId, @@ -73,7 +73,7 @@ pub async fn add_test_tables( .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), context_id)) .collect(); hummock_manager - .commit_epoch(epoch, CommitEpochInfo::for_test(ssts, sst_to_worker)) + .commit_epoch_for_test(epoch, ssts, sst_to_worker) .await .unwrap(); // Simulate a compaction and increase version by 1. @@ -148,7 +148,7 @@ pub async fn add_test_tables( .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), context_id)) .collect(); hummock_manager - .commit_epoch(epoch, CommitEpochInfo::for_test(ssts, sst_to_worker)) + .commit_epoch_for_test(epoch, ssts, sst_to_worker) .await .unwrap(); vec![test_tables, test_tables_2, test_tables_3] @@ -236,7 +236,7 @@ pub async fn register_table_ids_to_compaction_group( compaction_group_id: CompactionGroupId, ) { hummock_manager_ref - .register_table_ids( + .register_table_ids_for_test( &table_ids .iter() .map(|table_id| (*table_id, compaction_group_id)) @@ -251,8 +251,9 @@ pub async fn unregister_table_ids_from_compaction_group( table_ids: &[u32], ) { hummock_manager_ref - .unregister_table_ids_fail_fast(table_ids) - .await; + .unregister_table_ids(table_ids.iter().map(|table_id| TableId::new(*table_id))) + .await + .unwrap(); } /// Generate keys like `001_key_test_00002` with timestamp `epoch`. @@ -378,13 +379,13 @@ pub async fn commit_from_meta_node( hummock_manager_ref: &HummockManager, epoch: HummockEpoch, ssts: Vec, -) -> crate::hummock::error::Result> { +) -> crate::hummock::error::Result<()> { let sst_to_worker = ssts .iter() .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), META_NODE_ID)) .collect(); hummock_manager_ref - .commit_epoch(epoch, CommitEpochInfo::for_test(ssts, sst_to_worker)) + .commit_epoch_for_test(epoch, ssts, sst_to_worker) .await } @@ -401,7 +402,7 @@ pub async fn add_ssts( .map(|LocalSstableInfo { sst_info, .. }| (sst_info.get_object_id(), context_id)) .collect(); hummock_manager - .commit_epoch(epoch, CommitEpochInfo::for_test(ssts, sst_to_worker)) + .commit_epoch_for_test(epoch, ssts, sst_to_worker) .await .unwrap(); test_tables diff --git a/src/meta/src/hummock/vacuum.rs b/src/meta/src/hummock/vacuum.rs index 9e45fa5a2bcc9..6cde13507836b 100644 --- a/src/meta/src/hummock/vacuum.rs +++ b/src/meta/src/hummock/vacuum.rs @@ -95,7 +95,7 @@ impl VacuumManager { pending_object_ids } else { // 2. If no pending SST objects, then fetch new ones. - let mut objects_to_delete = self.hummock_manager.get_objects_to_delete().await; + let mut objects_to_delete = self.hummock_manager.get_objects_to_delete(); self.filter_out_pinned_ssts(&mut objects_to_delete).await?; if objects_to_delete.is_empty() { return Ok(vec![]); @@ -237,13 +237,13 @@ mod tests { assert_eq!(vacuum.vacuum_metadata().await.unwrap(), 6); assert_eq!(vacuum.vacuum_metadata().await.unwrap(), 0); - assert!(hummock_manager.get_objects_to_delete().await.is_empty()); + assert!(hummock_manager.get_objects_to_delete().is_empty()); hummock_manager .unpin_version_before(context_id, HummockVersionId::MAX) .await .unwrap(); hummock_manager.create_version_checkpoint(0).await.unwrap(); - assert!(!hummock_manager.get_objects_to_delete().await.is_empty()); + assert!(!hummock_manager.get_objects_to_delete().is_empty()); // No SST deletion is scheduled because no available worker. assert_eq!(vacuum.vacuum_object().await.unwrap().len(), 0); let _receiver = compactor_manager.add_compactor(context_id); diff --git a/src/meta/src/manager/catalog/database.rs b/src/meta/src/manager/catalog/database.rs index aa4317add003b..299065fe9586d 100644 --- a/src/meta/src/manager/catalog/database.rs +++ b/src/meta/src/manager/catalog/database.rs @@ -18,17 +18,19 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use itertools::Itertools; use risingwave_common::bail; use risingwave_common::catalog::TableOption; +use risingwave_pb::catalog::subscription::PbSubscriptionState; use risingwave_pb::catalog::table::TableType; use risingwave_pb::catalog::{ - Connection, CreateType, Database, Function, Index, PbStreamJobStatus, Schema, Sink, Source, - StreamJobStatus, Subscription, Table, View, + Connection, CreateType, Database, Function, Index, PbStreamJobStatus, Schema, Secret, Sink, + Source, StreamJobStatus, Subscription, Table, View, }; use risingwave_pb::data::DataType; use risingwave_pb::user::grant_privilege::PbObject; +use super::utils::{get_refed_secret_ids_from_sink, get_refed_secret_ids_from_source}; use super::{ - ConnectionId, DatabaseId, FunctionId, RelationId, SchemaId, SinkId, SourceId, SubscriptionId, - ViewId, + ConnectionId, DatabaseId, FunctionId, RelationId, SchemaId, SecretId, SinkId, SourceId, + SubscriptionId, ViewId, }; use crate::manager::{IndexId, MetaSrvEnv, TableId, UserId}; use crate::model::MetadataModel; @@ -45,6 +47,7 @@ pub type Catalog = ( Vec, Vec, Vec, + Vec, ); type DatabaseKey = String; @@ -75,10 +78,16 @@ pub struct DatabaseManager { pub(super) functions: BTreeMap, /// Cached connection information. pub(super) connections: BTreeMap, + /// Cached secret information. + pub(super) secrets: BTreeMap, /// Relation reference count mapping. - // TODO(zehua): avoid key conflicts after distinguishing table's and source's id generator. pub(super) relation_ref_count: HashMap, + + /// Secret reference count mapping + pub(super) secret_ref_count: HashMap, + /// Connection reference count mapping. + pub(super) connection_ref_count: HashMap, // In-progress creation tracker. pub(super) in_progress_creation_tracker: HashSet, // In-progress creating streaming job tracker: this is a temporary workaround to avoid clean up @@ -100,8 +109,11 @@ impl DatabaseManager { let functions = Function::list(env.meta_store().as_kv()).await?; let connections = Connection::list(env.meta_store().as_kv()).await?; let subscriptions = Subscription::list(env.meta_store().as_kv()).await?; + let secrets = Secret::list(env.meta_store().as_kv()).await?; let mut relation_ref_count = HashMap::new(); + let mut connection_ref_count = HashMap::new(); + let mut secret_ref_count = HashMap::new(); let databases = BTreeMap::from_iter( databases @@ -110,24 +122,35 @@ impl DatabaseManager { ); let schemas = BTreeMap::from_iter(schemas.into_iter().map(|schema| (schema.id, schema))); let sources = BTreeMap::from_iter(sources.into_iter().map(|source| { - // TODO(weili): wait for yezizp to refactor ref cnt if let Some(connection_id) = source.connection_id { - *relation_ref_count.entry(connection_id).or_default() += 1; + *connection_ref_count.entry(connection_id).or_default() += 1; } (source.id, source) })); + for source in sources.values() { + for secret_id in get_refed_secret_ids_from_source(source)? { + *secret_ref_count.entry(secret_id).or_default() += 1; + } + } let sinks = BTreeMap::from_iter(sinks.into_iter().map(|sink| { for depend_relation_id in &sink.dependent_relations { *relation_ref_count.entry(*depend_relation_id).or_default() += 1; } + if let Some(connection_id) = sink.connection_id { + *connection_ref_count.entry(connection_id).or_default() += 1; + } + for secret_id in get_refed_secret_ids_from_sink(&sink) { + *secret_ref_count.entry(secret_id).or_default() += 1; + } (sink.id, sink) })); let subscriptions = BTreeMap::from_iter(subscriptions.into_iter().map(|subscription| { - for depend_relation_id in &subscription.dependent_relations { - *relation_ref_count.entry(*depend_relation_id).or_default() += 1; - } + *relation_ref_count + .entry(subscription.dependent_table_id) + .or_default() += 1; (subscription.id, subscription) })); + let secrets = BTreeMap::from_iter(secrets.into_iter().map(|secret| (secret.id, secret))); let indexes = BTreeMap::from_iter(indexes.into_iter().map(|index| (index.id, index))); let tables = BTreeMap::from_iter(tables.into_iter().map(|table| { for depend_relation_id in &table.dependent_relations { @@ -144,6 +167,8 @@ impl DatabaseManager { let functions = BTreeMap::from_iter(functions.into_iter().map(|f| (f.id, f))); let connections = BTreeMap::from_iter(connections.into_iter().map(|c| (c.id, c))); + // todo: scan over stream source info and sink to update secret ref count `_secret_ref_count` + Ok(Self { databases, schemas, @@ -156,6 +181,9 @@ impl DatabaseManager { functions, connections, relation_ref_count, + connection_ref_count, + secrets, + secret_ref_count, in_progress_creation_tracker: HashSet::default(), in_progress_creation_streaming_job: HashMap::default(), in_progress_creating_tables: HashMap::default(), @@ -166,14 +194,7 @@ impl DatabaseManager { ( self.databases.values().cloned().collect_vec(), self.schemas.values().cloned().collect_vec(), - self.tables - .values() - .filter(|t| { - t.stream_job_status == PbStreamJobStatus::Unspecified as i32 - || t.stream_job_status == PbStreamJobStatus::Created as i32 - }) - .cloned() - .collect_vec(), + self.tables.values().cloned().collect_vec(), self.sources.values().cloned().collect_vec(), self.sinks .values() @@ -185,10 +206,7 @@ impl DatabaseManager { .collect_vec(), self.subscriptions .values() - .filter(|t| { - t.stream_job_status == PbStreamJobStatus::Unspecified as i32 - || t.stream_job_status == PbStreamJobStatus::Created as i32 - }) + .filter(|t| t.subscription_state == PbSubscriptionState::Created as i32) .cloned() .collect_vec(), self.indexes @@ -202,6 +220,7 @@ impl DatabaseManager { self.views.values().cloned().collect_vec(), self.functions.values().cloned().collect_vec(), self.connections.values().cloned().collect_vec(), + self.secrets.values().cloned().collect_vec(), ) } @@ -294,10 +313,24 @@ impl DatabaseManager { } } + pub fn check_secret_name_duplicated(&self, secret_key: &RelationKey) -> MetaResult<()> { + if self.secrets.values().any(|x| { + x.database_id == secret_key.0 && x.schema_id == secret_key.1 && x.name.eq(&secret_key.2) + }) { + Err(MetaError::catalog_duplicated("secret", &secret_key.2)) + } else { + Ok(()) + } + } + pub fn list_databases(&self) -> Vec { self.databases.values().cloned().collect_vec() } + pub fn list_schemas(&self) -> Vec { + self.schemas.values().cloned().collect_vec() + } + pub fn list_creating_background_mvs(&self) -> Vec
{ self.tables .values() @@ -322,6 +355,10 @@ impl DatabaseManager { self.tables.values().cloned().collect_vec() } + pub fn list_secrets(&self) -> Vec { + self.secrets.values().cloned().collect_vec() + } + pub fn get_table(&self, table_id: TableId) -> Option<&Table> { self.tables.get(&table_id) } @@ -361,6 +398,14 @@ impl DatabaseManager { .collect_vec() } + pub fn list_view_ids(&self, schema_id: SchemaId) -> Vec { + self.views + .values() + .filter(|view| view.schema_id == schema_id) + .map(|view| view.id) + .collect_vec() + } + pub fn list_sources(&self) -> Vec { self.sources.values().cloned().collect_vec() } @@ -398,7 +443,6 @@ impl DatabaseManager { .keys() .copied() .chain(self.sinks.keys().copied()) - .chain(self.subscriptions.keys().copied()) .chain(self.indexes.keys().copied()) .chain(self.sources.keys().copied()) .chain( @@ -442,11 +486,11 @@ impl DatabaseManager { && self.views.values().all(|v| v.schema_id != schema_id) } - pub fn increase_ref_count(&mut self, relation_id: RelationId) { + pub fn increase_relation_ref_count(&mut self, relation_id: RelationId) { *self.relation_ref_count.entry(relation_id).or_insert(0) += 1; } - pub fn decrease_ref_count(&mut self, relation_id: RelationId) { + pub fn decrease_relation_ref_count(&mut self, relation_id: RelationId) { match self.relation_ref_count.entry(relation_id) { Entry::Occupied(mut o) => { *o.get_mut() -= 1; @@ -458,6 +502,38 @@ impl DatabaseManager { } } + pub fn increase_secret_ref_count(&mut self, secret_id: SecretId) { + *self.secret_ref_count.entry(secret_id).or_insert(0) += 1; + } + + pub fn decrease_secret_ref_count(&mut self, secret_id: SecretId) { + match self.secret_ref_count.entry(secret_id) { + Entry::Occupied(mut o) => { + *o.get_mut() -= 1; + if *o.get() == 0 { + o.remove_entry(); + } + } + Entry::Vacant(_) => unreachable!(), + } + } + + pub fn increase_connection_ref_count(&mut self, connection_id: ConnectionId) { + *self.connection_ref_count.entry(connection_id).or_insert(0) += 1; + } + + pub fn decrease_connection_ref_count(&mut self, connection_id: ConnectionId) { + match self.connection_ref_count.entry(connection_id) { + Entry::Occupied(mut o) => { + *o.get_mut() -= 1; + if *o.get() == 0 { + o.remove_entry(); + } + } + Entry::Vacant(_) => unreachable!(), + } + } + pub fn has_creation_in_database(&self, database_id: DatabaseId) -> bool { self.in_progress_creation_tracker .iter() @@ -610,7 +686,6 @@ impl DatabaseManager { } } - // TODO(zehua): refactor when using SourceId. pub fn ensure_table_view_or_source_id(&self, table_id: &TableId) -> MetaResult<()> { if self.tables.contains_key(table_id) || self.sources.contains_key(table_id) diff --git a/src/meta/src/manager/catalog/fragment.rs b/src/meta/src/manager/catalog/fragment.rs index feb3dc3026dbf..1ac5aacef450a 100644 --- a/src/meta/src/manager/catalog/fragment.rs +++ b/src/meta/src/manager/catalog/fragment.rs @@ -18,21 +18,26 @@ use std::sync::Arc; use anyhow::{anyhow, Context}; use itertools::Itertools; use risingwave_common::bail; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::catalog::TableId; use risingwave_common::hash::{ActorMapping, ParallelUnitId, ParallelUnitMapping}; -use risingwave_common::util::stream_graph_visitor::{visit_stream_node, visit_stream_node_cont}; +use risingwave_common::util::stream_graph_visitor::{ + visit_stream_node, visit_stream_node_cont, visit_stream_node_cont_mut, +}; +use risingwave_common::util::worker_util::WorkerNodeId; use risingwave_connector::source::SplitImpl; use risingwave_meta_model_v2::SourceId; +use risingwave_pb::common::{PbParallelUnitMapping, PbWorkerSlotMapping}; use risingwave_pb::meta::subscribe_response::{Info, Operation}; use risingwave_pb::meta::table_fragments::actor_status::ActorState; use risingwave_pb::meta::table_fragments::{ActorStatus, Fragment, State}; -use risingwave_pb::meta::FragmentParallelUnitMapping; +use risingwave_pb::meta::FragmentWorkerSlotMapping; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::update_mutation::MergeUpdate; use risingwave_pb::stream_plan::{ DispatchStrategy, Dispatcher, DispatcherType, FragmentTypeFlag, StreamActor, StreamNode, }; +use risingwave_pb::stream_service::BuildActorInfo; use tokio::sync::{RwLock, RwLockReadGuard}; use crate::barrier::Reschedule; @@ -40,10 +45,10 @@ use crate::manager::cluster::WorkerId; use crate::manager::{commit_meta, commit_meta_with_trx, LocalNotification, MetaSrvEnv}; use crate::model::{ ActorId, BTreeMapTransaction, FragmentId, MetadataModel, MigrationPlan, TableFragments, - TableParallelism, ValTransaction, + TableParallelism, }; use crate::storage::Transaction; -use crate::stream::{SplitAssignment, TableRevision}; +use crate::stream::{to_build_actor_info, SplitAssignment, TableRevision}; use crate::{MetaError, MetaResult}; pub struct FragmentManagerCore { @@ -55,18 +60,24 @@ impl FragmentManagerCore { /// List all fragment vnode mapping info that not in `State::Initial`. pub fn all_running_fragment_mappings( &self, - ) -> impl Iterator + '_ { + ) -> impl Iterator + '_ { self.table_fragments .values() .filter(|tf| tf.state() != State::Initial) .flat_map(|table_fragments| { - table_fragments.fragments.values().map(|fragment| { - let parallel_unit_mapping = fragment.vnode_mapping.clone().unwrap(); - FragmentParallelUnitMapping { + table_fragments + .fragments + .values() + .map(move |fragment| FragmentWorkerSlotMapping { fragment_id: fragment.fragment_id, - mapping: Some(parallel_unit_mapping), - } - }) + mapping: Some( + FragmentManager::convert_mapping( + &table_fragments.actor_status, + fragment.vnode_mapping.as_ref().unwrap(), + ) + .unwrap(), + ), + }) }) } @@ -112,12 +123,21 @@ pub struct FragmentManager { core: RwLock, } +#[derive(Clone)] +pub struct InflightFragmentInfo { + pub actors: HashMap, + pub state_table_ids: HashSet, + pub is_injectable: bool, +} + pub struct ActorInfos { - /// `node_id` => `actor_ids` - pub actor_maps: HashMap>, + pub fragment_infos: HashMap, +} - /// all reachable barrier inject actors - pub barrier_inject_actor_maps: HashMap>, +impl ActorInfos { + pub fn new(fragment_infos: HashMap) -> Self { + Self { fragment_infos } + } } pub type FragmentManagerRef = Arc; @@ -191,18 +211,31 @@ impl FragmentManager { async fn notify_fragment_mapping(&self, table_fragment: &TableFragments, operation: Operation) { // Notify all fragment mapping to frontend nodes for fragment in table_fragment.fragments.values() { - let mapping = fragment + let vnode_mapping = fragment .vnode_mapping - .clone() + .as_ref() .expect("no data distribution found"); - let fragment_mapping = FragmentParallelUnitMapping { - fragment_id: fragment.fragment_id, - mapping: Some(mapping), + + let fragment_mapping = if let Operation::Delete = operation { + FragmentWorkerSlotMapping { + fragment_id: fragment.fragment_id, + mapping: None, + } + } else { + FragmentWorkerSlotMapping { + fragment_id: fragment.fragment_id, + mapping: Some( + Self::convert_mapping(&table_fragment.actor_status, vnode_mapping).unwrap(), + ), + } }; self.env .notification_manager() - .notify_frontend(operation, Info::ParallelUnitMapping(fragment_mapping)) + .notify_frontend( + operation, + Info::StreamingWorkerSlotMapping(fragment_mapping), + ) .await; } @@ -560,7 +593,6 @@ impl FragmentManager { .filter_map(|table_id| map.get(table_id).cloned()) .collect_vec(); - let mut dirty_sink_into_table_upstream_fragment_id = HashSet::new(); let mut table_fragments = BTreeMapTransaction::new(map); let mut table_ids_to_unregister_from_hummock = vec![]; for table_fragment in &to_delete_table_fragments { @@ -595,32 +627,14 @@ impl FragmentManager { }) }); } - - if let Some(sink_fragment) = table_fragment.sink_fragment() { - let dispatchers = sink_fragment - .get_actors() - .iter() - .map(|actor| actor.get_dispatcher()) - .collect_vec(); - - if !dispatchers.is_empty() { - dirty_sink_into_table_upstream_fragment_id.insert(sink_fragment.fragment_id); - } - } } - if !dirty_sink_into_table_upstream_fragment_id.is_empty() { - let to_delete_table_ids: HashSet<_> = to_delete_table_fragments - .iter() - .map(|table| table.table_id()) - .collect(); - - Self::clean_dirty_table_sink_downstreams( - dirty_sink_into_table_upstream_fragment_id, - to_delete_table_ids, - &mut table_fragments, - )?; - } + let to_delete_table_ids: HashSet<_> = to_delete_table_fragments + .iter() + .map(|table| table.table_id()) + .collect(); + + Self::clean_dirty_table_sink_downstreams(to_delete_table_ids, &mut table_fragments)?; if table_ids.is_empty() { commit_meta!(self, table_fragments)?; @@ -646,36 +660,43 @@ impl FragmentManager { // but the union branch that attaches the downstream table to the sink fragment may still exist. // This could lead to issues. Therefore, we need to find the sink fragment’s downstream, then locate its union node and delete the dirty merge. fn clean_dirty_table_sink_downstreams( - dirty_sink_into_table_upstream_fragment_id: HashSet, to_delete_table_ids: HashSet, table_fragments: &mut BTreeMapTransaction<'_, TableId, TableFragments>, ) -> MetaResult<()> { tracing::info!("cleaning dirty downstream merge nodes for table sink"); + let mut all_fragment_ids = HashSet::new(); + + for (table_id, table_fragment) in table_fragments.tree_ref() { + if to_delete_table_ids.contains(table_id) { + continue; + } + + all_fragment_ids.extend(table_fragment.fragment_ids()); + } + let mut dirty_downstream_table_ids = HashMap::new(); - for (table_id, table_fragment) in table_fragments.tree_mut() { + + for (table_id, table_fragment) in table_fragments.tree_ref() { if to_delete_table_ids.contains(table_id) { continue; } - for fragment in table_fragment.fragments.values_mut() { + for fragment in table_fragment.fragments.values() { if fragment .get_upstream_fragment_ids() .iter() - .all(|upstream_fragment_id| { - !dirty_sink_into_table_upstream_fragment_id.contains(upstream_fragment_id) - }) + .all(|upstream_fragment_id| all_fragment_ids.contains(upstream_fragment_id)) { continue; } - for actor in &mut fragment.actors { - visit_stream_node_cont(actor.nodes.as_mut().unwrap(), |node| { + for actor in &fragment.actors { + visit_stream_node_cont(actor.nodes.as_ref().unwrap(), |node| { if let Some(NodeBody::Union(_)) = node.node_body { - for input in &mut node.input { - if let Some(NodeBody::Merge(merge_node)) = &mut input.node_body - && dirty_sink_into_table_upstream_fragment_id - .contains(&merge_node.upstream_fragment_id) + for input in &node.input { + if let Some(NodeBody::Merge(merge_node)) = &input.node_body + && !all_fragment_ids.contains(&merge_node.upstream_fragment_id) { dirty_downstream_table_ids .insert(*table_id, fragment.fragment_id); @@ -686,12 +707,6 @@ impl FragmentManager { true }) } - - fragment - .upstream_fragment_ids - .retain(|upstream_fragment_id| { - !dirty_sink_into_table_upstream_fragment_id.contains(upstream_fragment_id) - }); } } @@ -705,13 +720,16 @@ impl FragmentManager { .get_mut(&fragment_id) .with_context(|| format!("fragment not exist: id={}", fragment_id))?; + fragment + .upstream_fragment_ids + .retain(|upstream_fragment_id| all_fragment_ids.contains(upstream_fragment_id)); + for actor in &mut fragment.actors { - visit_stream_node_cont(actor.nodes.as_mut().unwrap(), |node| { + visit_stream_node_cont_mut(actor.nodes.as_mut().unwrap(), |node| { if let Some(NodeBody::Union(_)) = node.node_body { node.input.retain_mut(|input| { if let Some(NodeBody::Merge(merge_node)) = &mut input.node_body - && dirty_sink_into_table_upstream_fragment_id - .contains(&merge_node.upstream_fragment_id) + && !all_fragment_ids.contains(&merge_node.upstream_fragment_id) { false } else { @@ -729,39 +747,45 @@ impl FragmentManager { /// Used in [`crate::barrier::GlobalBarrierManager`], load all running actor that need to be sent or /// collected pub async fn load_all_actors(&self) -> ActorInfos { - let mut actor_maps = HashMap::new(); - let mut barrier_inject_actor_maps = HashMap::new(); + let mut fragment_infos = HashMap::new(); let map = &self.core.read().await.table_fragments; for fragments in map.values() { - for (worker_id, actor_states) in fragments.worker_actor_states() { - for (actor_id, actor_state) in actor_states { - if actor_state == ActorState::Running { - actor_maps - .entry(worker_id) - .or_insert_with(Vec::new) - .push(actor_id); - } - } - } - - let barrier_inject_actors = fragments.worker_barrier_inject_actor_states(); - for (worker_id, actor_states) in barrier_inject_actors { - for (actor_id, actor_state) in actor_states { - if actor_state == ActorState::Running { - barrier_inject_actor_maps - .entry(worker_id) - .or_insert_with(Vec::new) - .push(actor_id); - } - } + for fragment in fragments.fragments.values() { + let info = InflightFragmentInfo { + actors: fragment + .actors + .iter() + .filter_map(|actor| { + let status = fragments + .actor_status + .get(&actor.actor_id) + .expect("should exist"); + if status.state == ActorState::Running as i32 { + Some(( + actor.actor_id, + status + .get_parallel_unit() + .expect("should set") + .worker_node_id, + )) + } else { + None + } + }) + .collect(), + state_table_ids: fragment + .state_table_ids + .iter() + .map(|table_id| TableId::new(*table_id)) + .collect(), + is_injectable: TableFragments::is_injectable(fragment.fragment_type_mask), + }; + assert!(fragment_infos.insert(fragment.fragment_id, info,).is_none()); } } - ActorInfos { - actor_maps, - barrier_inject_actor_maps, - } + ActorInfos::new(fragment_infos) } async fn migrate_fragment_actors_inner( @@ -849,14 +873,20 @@ impl FragmentManager { pub async fn all_node_actors( &self, include_inactive: bool, - ) -> HashMap> { + subscriptions: &HashMap>, + ) -> HashMap> { let mut actor_maps = HashMap::new(); let map = &self.core.read().await.table_fragments; for fragments in map.values() { + let table_id = fragments.table_id(); for (node_id, actors) in fragments.worker_actors(include_inactive) { let node_actors = actor_maps.entry(node_id).or_insert_with(Vec::new); - node_actors.extend(actors); + node_actors.extend( + actors + .into_iter() + .map(|actor| to_build_actor_info(actor, subscriptions, table_id)), + ); } } @@ -1270,11 +1300,14 @@ impl FragmentManager { *fragment.vnode_mapping.as_mut().unwrap() = vnode_mapping.clone(); + let worker_slot_mapping = Self::convert_mapping(&actor_status, &vnode_mapping)?; + // Notify fragment mapping to frontend nodes. - let fragment_mapping = FragmentParallelUnitMapping { + let fragment_mapping = FragmentWorkerSlotMapping { fragment_id: *fragment_id as FragmentId, - mapping: Some(vnode_mapping), + mapping: Some(worker_slot_mapping), }; + fragment_mapping_to_notify.push(fragment_mapping); } @@ -1394,13 +1427,30 @@ impl FragmentManager { for mapping in fragment_mapping_to_notify { self.env .notification_manager() - .notify_frontend(Operation::Update, Info::ParallelUnitMapping(mapping)) + .notify_frontend(Operation::Update, Info::StreamingWorkerSlotMapping(mapping)) .await; } Ok(()) } + fn convert_mapping( + actor_status: &BTreeMap, + vnode_mapping: &PbParallelUnitMapping, + ) -> MetaResult { + let parallel_unit_to_worker = actor_status + .values() + .map(|actor_status| { + let parallel_unit = actor_status.get_parallel_unit().unwrap(); + (parallel_unit.id, parallel_unit.worker_node_id) + }) + .collect(); + + Ok(ParallelUnitMapping::from_protobuf(vnode_mapping) + .to_worker_slot(¶llel_unit_to_worker)? + .to_protobuf()) + } + pub async fn table_node_actors( &self, table_ids: &HashSet, diff --git a/src/meta/src/manager/catalog/mod.rs b/src/meta/src/manager/catalog/mod.rs index 5a316696e6195..b45787a6c3e9c 100644 --- a/src/meta/src/manager/catalog/mod.rs +++ b/src/meta/src/manager/catalog/mod.rs @@ -19,7 +19,6 @@ mod utils; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::iter; -use std::option::Option::Some; use std::sync::Arc; use anyhow::{anyhow, Context}; @@ -27,18 +26,19 @@ pub use database::*; pub use fragment::*; use itertools::Itertools; use risingwave_common::catalog::{ - is_subscription_internal_table, valid_table_name, TableId as StreamingJobId, TableOption, - DEFAULT_DATABASE_NAME, DEFAULT_SCHEMA_NAME, DEFAULT_SUPER_USER, DEFAULT_SUPER_USER_FOR_PG, + valid_table_name, TableId as StreamingJobId, TableOption, DEFAULT_DATABASE_NAME, + DEFAULT_SCHEMA_NAME, DEFAULT_SUPER_USER, DEFAULT_SUPER_USER_FOR_PG, DEFAULT_SUPER_USER_FOR_PG_ID, DEFAULT_SUPER_USER_ID, SYSTEM_SCHEMAS, }; -use risingwave_common::{bail, ensure}; +use risingwave_common::{bail, current_cluster_version, ensure}; use risingwave_connector::source::{should_copy_to_format_encode_options, UPSTREAM_SOURCE_KEY}; +use risingwave_pb::catalog::subscription::PbSubscriptionState; use risingwave_pb::catalog::table::{OptionalAssociatedSourceId, TableType}; use risingwave_pb::catalog::{ Comment, Connection, CreateType, Database, Function, Index, PbSource, PbStreamJobStatus, - Schema, Sink, Source, StreamJobStatus, Subscription, Table, View, + Schema, Secret, Sink, Source, StreamJobStatus, Subscription, Table, View, }; -use risingwave_pb::ddl_service::{alter_owner_request, alter_set_schema_request}; +use risingwave_pb::ddl_service::{alter_owner_request, alter_set_schema_request, TableJobType}; use risingwave_pb::meta::subscribe_response::{Info, Operation}; use risingwave_pb::user::grant_privilege::{Action, ActionWithGrantOption, Object}; use risingwave_pb::user::update_user_request::UpdateField; @@ -46,10 +46,11 @@ use risingwave_pb::user::{GrantPrivilege, UserInfo}; use tokio::sync::{Mutex, MutexGuard}; use user::*; +pub use self::utils::{get_refed_secret_ids_from_sink, get_refed_secret_ids_from_source}; use crate::manager::{ IdCategory, MetaSrvEnv, NotificationVersion, StreamingJob, IGNORED_NOTIFICATION_VERSION, }; -use crate::model::{BTreeMapTransaction, MetadataModel, TableFragments, ValTransaction}; +use crate::model::{BTreeMapTransaction, MetadataModel, TableFragments}; use crate::storage::Transaction; use crate::{MetaError, MetaResult}; @@ -63,6 +64,7 @@ pub type RelationId = u32; pub type IndexId = u32; pub type ViewId = u32; pub type FunctionId = u32; +pub type SecretId = u32; pub type UserId = u32; pub type ConnectionId = u32; @@ -83,6 +85,7 @@ macro_rules! commit_meta_with_trx { { use tracing::Instrument; use $crate::storage::meta_store::MetaStore; + use $crate::model::{InMemValTransaction, ValTransaction}; async { // Apply the change in `ValTransaction` to trx $( @@ -126,6 +129,10 @@ use risingwave_pb::meta::relation::RelationInfo; use risingwave_pb::meta::{Relation, RelationGroup}; pub(crate) use {commit_meta, commit_meta_with_trx}; +use self::utils::{ + refcnt_dec_sink_secret_ref, refcnt_dec_source_secret_ref, refcnt_inc_sink_secret_ref, + refcnt_inc_source_secret_ref, +}; use crate::controller::rename::{ alter_relation_rename, alter_relation_rename_refs, ReplaceTableExprRewriter, }; @@ -185,6 +192,7 @@ impl CatalogManager { /// We identify a 'legacy' source based on two conditions: /// 1. The `format_encode_options` in `source_info` is empty. /// 2. Keys with certain prefixes belonging to `format_encode_options` exist in `with_properties` instead. + /// /// And if the source is identified as 'legacy', we copy the misplaced keys from `with_properties` to `format_encode_options`. async fn source_backward_compat_check(&self) -> MetaResult<()> { let core = &mut *self.core.lock().await; @@ -327,6 +335,7 @@ impl CatalogManager { let mut users = BTreeMapTransaction::new(&mut user_core.user_info); let mut functions = BTreeMapTransaction::new(&mut database_core.functions); let mut connections = BTreeMapTransaction::new(&mut database_core.connections); + let mut secrets = BTreeMapTransaction::new(&mut database_core.secrets); /// `drop_by_database_id` provides a wrapper for dropping relations by database id, it will /// return the relation ids that dropped. @@ -359,6 +368,7 @@ impl CatalogManager { let views_to_drop = drop_by_database_id!(views, database_id); let functions_to_drop = drop_by_database_id!(functions, database_id); let connections_to_drop = drop_by_database_id!(connections, database_id); + let secrets_to_drop = drop_by_database_id!(secrets, database_id); connections_dropped = connections_to_drop.clone(); let objects = std::iter::once(Object::DatabaseId(database_id)) @@ -420,6 +430,7 @@ impl CatalogManager { .iter() .map(|connection| connection.owner), ) + .chain(secrets_to_drop.iter().map(|secret| secret.owner)) .for_each(|owner_id| user_core.decrease_ref(owner_id)); // Update relation ref count. @@ -432,9 +443,8 @@ impl CatalogManager { for view in &views_to_drop { database_core.relation_ref_count.remove(&view.id); } - // TODO(weili): wait for yezizp to refactor ref cnt for connection in &connections_to_drop { - database_core.relation_ref_count.remove(&connection.id); + database_core.connection_ref_count.remove(&connection.id); } for user in users_need_update { self.notify_frontend(Operation::Update, Info::User(user)) @@ -477,6 +487,57 @@ impl CatalogManager { } } + pub async fn create_secret(&self, secret: Secret) -> MetaResult { + let core = &mut *self.core.lock().await; + let database_core = &mut core.database; + let user_core = &mut core.user; + database_core.ensure_database_id(secret.database_id)?; + database_core.ensure_schema_id(secret.schema_id)?; + #[cfg(not(test))] + user_core.ensure_user_id(secret.owner)?; + let key = ( + secret.database_id as DatabaseId, + secret.schema_id as SchemaId, + secret.name.clone(), + ); + database_core.check_secret_name_duplicated(&key)?; + + let secret_id = secret.id; + let mut secret_entry = BTreeMapTransaction::new(&mut database_core.secrets); + secret_entry.insert(secret_id, secret.to_owned()); + commit_meta!(self, secret_entry)?; + + user_core.increase_ref(secret.owner); + + let version = self + .notify_frontend(Operation::Add, Info::Secret(secret)) + .await; + Ok(version) + } + + pub async fn drop_secret(&self, secret_id: SecretId) -> MetaResult { + let core = &mut *self.core.lock().await; + let database_core = &mut core.database; + let user_core = &mut core.user; + let mut secrets = BTreeMapTransaction::new(&mut database_core.secrets); + + // todo: impl a ref count check for secret + // if secret is used by other relations, not found in the catalog or do not have the privilege to drop, return error + // else: commit the change and notify frontend + + let secret = secrets + .remove(secret_id) + .ok_or_else(|| anyhow!("secret not found"))?; + + commit_meta!(self, secrets)?; + user_core.decrease_ref(secret.owner); + + let version = self + .notify_frontend(Operation::Delete, Info::Secret(secret)) + .await; + Ok(version) + } + pub async fn create_connection( &self, connection: Connection, @@ -520,12 +581,11 @@ impl CatalogManager { let user_core = &mut core.user; let mut connections = BTreeMapTransaction::new(&mut database_core.connections); - // TODO(weili): wait for yezizp to refactor ref cnt - match database_core.relation_ref_count.get(&conn_id) { + match database_core.connection_ref_count.get(&conn_id) { Some(ref_count) => { let connection_name = connections .get(&conn_id) - .ok_or_else(|| anyhow!("connection not found"))? + .ok_or_else(|| MetaError::catalog_id_not_found("connection", conn_id))? .name .clone(); Err(MetaError::permission_denied(format!( @@ -536,7 +596,7 @@ impl CatalogManager { None => { let connection = connections .remove(conn_id) - .ok_or_else(|| anyhow!("connection not found"))?; + .ok_or_else(|| MetaError::catalog_id_not_found("connection", conn_id))?; commit_meta!(self, connections)?; user_core.decrease_ref(connection.owner); @@ -632,7 +692,7 @@ impl CatalogManager { user_core.increase_ref(view.owner); for &dependent_relation_id in &view.dependent_relations { - database_core.increase_ref_count(dependent_relation_id); + database_core.increase_relation_ref_count(dependent_relation_id); } let version = self @@ -681,7 +741,7 @@ impl CatalogManager { let function = functions .remove(function_id) - .ok_or_else(|| anyhow!("function not found"))?; + .ok_or_else(|| MetaError::catalog_id_not_found("function", function_id))?; let objects = &[Object::FunctionId(function_id)]; let users_need_update = Self::update_user_privileges(&mut users, objects); @@ -711,13 +771,10 @@ impl CatalogManager { ) -> MetaResult<()> { match stream_job { StreamingJob::MaterializedView(table) => { - self.start_create_table_procedure(table, internal_tables) + self.start_create_materialized_view_procedure(table, internal_tables) .await } StreamingJob::Sink(sink, _) => self.start_create_sink_procedure(sink).await, - StreamingJob::Subscription(subscription) => { - self.start_create_subscription_procedure(subscription).await - } StreamingJob::Index(index, index_table) => { self.start_create_index_procedure(index, index_table).await } @@ -726,8 +783,7 @@ impl CatalogManager { self.start_create_table_procedure_with_source(source, table) .await } else { - self.start_create_table_procedure(table, internal_tables) - .await + self.start_create_table_procedure(table).await } } StreamingJob::Source(source) => self.start_create_source_procedure(source).await, @@ -780,8 +836,37 @@ impl CatalogManager { .await; } - /// This is used for both `CREATE TABLE` and `CREATE MATERIALIZED VIEW`. - pub async fn start_create_table_procedure( + /// This is used for both `CREATE TABLE` + pub async fn start_create_table_procedure(&self, table: &Table) -> MetaResult<()> { + let core = &mut *self.core.lock().await; + let database_core = &mut core.database; + let user_core = &mut core.user; + database_core.ensure_database_id(table.database_id)?; + database_core.ensure_schema_id(table.schema_id)?; + for dependent_id in &table.dependent_relations { + database_core.ensure_table_view_or_source_id(dependent_id)?; + } + #[cfg(not(test))] + user_core.ensure_user_id(table.owner)?; + let key = (table.database_id, table.schema_id, table.name.clone()); + + database_core.check_relation_name_duplicated(&key)?; + + if database_core.has_in_progress_creation(&key) { + bail!("table is in creating procedure"); + } else { + database_core.mark_creating(&key); + database_core.mark_creating_streaming_job(table.id, key); + for &dependent_relation_id in &table.dependent_relations { + database_core.increase_relation_ref_count(dependent_relation_id); + } + user_core.increase_ref(table.owner); + Ok(()) + } + } + + /// This is used for `CREATE MATERIALIZED VIEW`. + pub async fn start_create_materialized_view_procedure( &self, table: &Table, internal_tables: Vec
, @@ -792,7 +877,6 @@ impl CatalogManager { database_core.ensure_database_id(table.database_id)?; database_core.ensure_schema_id(table.schema_id)?; for dependent_id in &table.dependent_relations { - // TODO(zehua): refactor when using SourceId. database_core.ensure_table_view_or_source_id(dependent_id)?; } #[cfg(not(test))] @@ -806,16 +890,31 @@ impl CatalogManager { !tables.contains_key(&table.id), "table must not already exist in meta" ); - for table in internal_tables { - tables.insert(table.id, table); + for table in &internal_tables { + tables.insert(table.id, table.clone()); } tables.insert(table.id, table.clone()); commit_meta!(self, tables)?; for &dependent_relation_id in &table.dependent_relations { - database_core.increase_ref_count(dependent_relation_id); + database_core.increase_relation_ref_count(dependent_relation_id); } user_core.increase_ref(table.owner); + let _version = self + .notify_frontend( + Operation::Add, + Info::RelationGroup(RelationGroup { + relations: vec![Relation { + relation_info: RelationInfo::Table(table.to_owned()).into(), + }] + .into_iter() + .chain(internal_tables.into_iter().map(|internal_table| Relation { + relation_info: RelationInfo::Table(internal_table).into(), + })) + .collect_vec(), + }), + ) + .await; Ok(()) } @@ -847,6 +946,7 @@ impl CatalogManager { /// with: /// 1. `stream_job_status` = CREATING /// 2. Not belonging to a background stream job. + /// /// Clean up these hanging tables by the id. pub async fn clean_dirty_tables(&self, fragment_manager: FragmentManagerRef) -> MetaResult<()> { let core = &mut *self.core.lock().await; @@ -1011,21 +1111,142 @@ impl CatalogManager { if table.table_type != TableType::Internal as i32 { // Recovered when init database manager. for relation_id in &table.dependent_relations { - database_core.decrease_ref_count(*relation_id); + database_core.decrease_relation_ref_count(*relation_id); } // Recovered when init user manager. tracing::debug!("decrease ref for {}", table.id); user_core.decrease_ref(table.owner); } } + // Notify frontend of cleaned tables. + let relations = tables_to_clean + .into_iter() + .map(|table| Relation { + relation_info: RelationInfo::Table(table).into(), + }) + .collect_vec(); + self.notify_frontend( + Operation::Delete, + Info::RelationGroup(RelationGroup { relations }), + ) + .await; Ok(()) } - /// This is used for both `CREATE TABLE` and `CREATE MATERIALIZED VIEW`. + /// `finish_stream_job` finishes a stream job and clean some states. + pub async fn finish_stream_job( + &self, + mut stream_job: StreamingJob, + internal_tables: Vec
, + ) -> MetaResult { + // 1. finish procedure. + let mut creating_internal_table_ids = internal_tables.iter().map(|t| t.id).collect_vec(); + + // Update the corresponding 'created_at' field. + stream_job.mark_created(); + + let version = match stream_job { + StreamingJob::MaterializedView(table) => { + creating_internal_table_ids.push(table.id); + self.finish_create_materialized_view_procedure(internal_tables, table) + .await? + } + StreamingJob::Sink(sink, target_table) => { + let sink_id = sink.id; + + let mut version = self + .finish_create_sink_procedure(internal_tables, sink) + .await?; + + if let Some((table, source)) = target_table { + version = self + .finish_replace_table_procedure(&source, &table, None, Some(sink_id), None) + .await?; + } + + version + } + StreamingJob::Table(source, table, ..) => { + creating_internal_table_ids.push(table.id); + if let Some(source) = source { + self.finish_create_table_procedure_with_source(source, table, internal_tables) + .await? + } else { + self.finish_create_table_procedure(internal_tables, table) + .await? + } + } + StreamingJob::Index(index, table) => { + creating_internal_table_ids.push(table.id); + self.finish_create_index_procedure(internal_tables, index, table) + .await? + } + StreamingJob::Source(source) => { + self.finish_create_source_procedure(source, internal_tables) + .await? + } + }; + + // 2. unmark creating tables. + self.unmark_creating_tables(&creating_internal_table_ids, false) + .await; + + Ok(version) + } + + /// This is used for `CREATE TABLE`. pub async fn finish_create_table_procedure( &self, mut internal_tables: Vec
, mut table: Table, + ) -> MetaResult { + let core = &mut *self.core.lock().await; + let database_core = &mut core.database; + let mut tables = BTreeMapTransaction::new(&mut database_core.tables); + let key = (table.database_id, table.schema_id, table.name.clone()); + assert!( + !tables.contains_key(&table.id) + && database_core.in_progress_creation_tracker.contains(&key), + "table must be in creating procedure" + ); + database_core.in_progress_creation_tracker.remove(&key); + database_core + .in_progress_creation_streaming_job + .remove(&table.id); + + table.stream_job_status = PbStreamJobStatus::Created.into(); + tables.insert(table.id, table.clone()); + for table in &mut internal_tables { + table.stream_job_status = PbStreamJobStatus::Created.into(); + tables.insert(table.id, table.clone()); + } + commit_meta!(self, tables)?; + + tracing::debug!(id = ?table.id, "notifying frontend"); + let version = self + .notify_frontend( + Operation::Add, + Info::RelationGroup(RelationGroup { + relations: vec![Relation { + relation_info: RelationInfo::Table(table.to_owned()).into(), + }] + .into_iter() + .chain(internal_tables.into_iter().map(|internal_table| Relation { + relation_info: RelationInfo::Table(internal_table).into(), + })) + .collect_vec(), + }), + ) + .await; + + Ok(version) + } + + /// This is used for `CREATE MATERIALIZED VIEW`. + pub async fn finish_create_materialized_view_procedure( + &self, + mut internal_tables: Vec
, + mut table: Table, ) -> MetaResult { let core = &mut *self.core.lock().await; let database_core = &mut core.database; @@ -1046,7 +1267,7 @@ impl CatalogManager { tracing::debug!(id = ?table.id, "notifying frontend"); let version = self .notify_frontend( - Operation::Add, + Operation::Update, Info::RelationGroup(RelationGroup { relations: vec![Relation { relation_info: RelationInfo::Table(table.to_owned()).into(), @@ -1063,73 +1284,110 @@ impl CatalogManager { Ok(version) } - /// Used to cleanup states in stream manager. + /// Used to cleanup `CREATE MATERIALIZED VIEW` state in stream manager. /// It is required because failure may not necessarily happen in barrier, /// e.g. when cordon nodes. /// and we still need some way to cleanup the state. /// /// Returns false if `table_id` is not found. - pub async fn cancel_create_table_procedure( + pub async fn cancel_create_materialized_view_procedure( &self, table_id: TableId, internal_table_ids: Vec, ) -> MetaResult { - let table = { - let core = &mut self.core.lock().await; - let database_core = &mut core.database; - let tables = &mut database_core.tables; - let Some(table) = tables.get(&table_id).cloned() else { - tracing::warn!( - "table_id {} missing when attempting to cancel job, could be cleaned on recovery", - table_id - ); - return Ok(false); - }; - // `Unspecified` maps to Created state, due to backwards compatibility. - // `Created` states should not be cancelled. - if table - .get_stream_job_status() - .unwrap_or(StreamJobStatus::Created) - != StreamJobStatus::Creating - { - return Err(MetaError::invalid_parameter(format!( - "table is not in creating state id={:#?}", - table_id - ))); + let core = &mut self.core.lock().await; + let database_core = &mut core.database; + let tables = &mut database_core.tables; + let Some(table) = tables.get(&table_id).cloned() else { + tracing::warn!( + "table_id {} missing when attempting to cancel job, could be cleaned on recovery", + table_id + ); + return Ok(false); + }; + let mut internal_tables = vec![]; + for internal_table_id in &internal_table_ids { + if let Some(table) = tables.get(internal_table_id) { + internal_tables.push(table.clone()); } + } + + // `Unspecified` maps to Created state, due to backwards compatibility. + // `Created` states should not be cancelled. + if table + .get_stream_job_status() + .unwrap_or(StreamJobStatus::Created) + != StreamJobStatus::Creating + { + return Err(MetaError::invalid_parameter(format!( + "table is not in creating state id={:#?}", + table_id + ))); + } - tracing::trace!("cleanup tables for {}", table.id); - let mut table_ids = vec![table.id]; - table_ids.extend(internal_table_ids); + tracing::trace!("cleanup tables for {}", table.id); + let mut table_ids = vec![table.id]; + table_ids.extend(internal_table_ids); - let tables = &mut database_core.tables; - let mut tables = BTreeMapTransaction::new(tables); - for table_id in table_ids { - let res = tables.remove(table_id); - assert!(res.is_some(), "table_id {} missing", table_id); - } - commit_meta!(self, tables)?; - table - }; + let tables = &mut database_core.tables; + let mut tables = BTreeMapTransaction::new(tables); + for table_id in table_ids { + let res = tables.remove(table_id); + assert!(res.is_some(), "table_id {} missing", table_id); + } + commit_meta!(self, tables)?; { - let core = &mut self.core.lock().await; - { - let user_core = &mut core.user; - user_core.decrease_ref(table.owner); - } + let user_core = &mut core.user; + user_core.decrease_ref(table.owner); + } - { - let database_core = &mut core.database; - for &dependent_relation_id in &table.dependent_relations { - database_core.decrease_ref_count(dependent_relation_id); - } + { + let database_core = &mut core.database; + for &dependent_relation_id in &table.dependent_relations { + database_core.decrease_relation_ref_count(dependent_relation_id); } } + // FIXME(kwannoel): Propagate version to fe + let _version = self + .notify_frontend( + Operation::Delete, + Info::RelationGroup(RelationGroup { + relations: vec![Relation { + relation_info: RelationInfo::Table(table.to_owned()).into(), + }] + .into_iter() + .chain(internal_tables.into_iter().map(|internal_table| Relation { + relation_info: RelationInfo::Table(internal_table).into(), + })) + .collect_vec(), + }), + ) + .await; + Ok(true) } + /// Used to cleanup `CREATE TABLE` state in stream manager. + pub async fn cancel_create_table_procedure(&self, table: &Table) { + let core = &mut *self.core.lock().await; + let database_core = &mut core.database; + let user_core = &mut core.user; + let key = (table.database_id, table.schema_id, table.name.clone()); + assert!( + !database_core.tables.contains_key(&table.id) + && database_core.has_in_progress_creation(&key), + "table must be in creating procedure" + ); + database_core.unmark_creating(&key); + database_core.unmark_creating_streaming_job(table.id); + for &dependent_relation_id in &table.dependent_relations { + database_core.decrease_relation_ref_count(dependent_relation_id); + } + user_core.decrease_ref(table.owner); + } + /// return id of streaming jobs in the database which need to be dropped by stream manager. pub async fn drop_relation( &self, @@ -1202,7 +1460,7 @@ impl CatalogManager { .tree_ref() .iter() .filter_map(|(_, subscription)| { - if subscription.dependent_relations.contains(&relation_id) { + if subscription.dependent_table_id == relation_id { Some(RelationInfo::Subscription(subscription.clone())) } else { None @@ -1549,11 +1807,6 @@ impl CatalogManager { if !all_subscription_ids.insert(subscription.id) { continue; } - let table_fragments = fragment_manager - .select_table_fragments_by_table_id(&subscription.id.into()) - .await?; - - all_internal_table_ids.extend(table_fragments.internal_table_ids()); if let Some(ref_count) = database_core .relation_ref_count @@ -1684,14 +1937,13 @@ impl CatalogManager { user_core.decrease_ref(index.owner); } - // `tables_removed` contains both index table and mv. + // `tables_removed` contains both index, table and mv. for table in &tables_removed { user_core.decrease_ref(table.owner); } for source in &sources_removed { user_core.decrease_ref(source.owner); - refcnt_dec_connection(database_core, source.connection_id); } for view in &views_removed { @@ -1714,30 +1966,31 @@ impl CatalogManager { // decrease dependent relations for table in &tables_removed { for dependent_relation_id in &table.dependent_relations { - database_core.decrease_ref_count(*dependent_relation_id); + database_core.decrease_relation_ref_count(*dependent_relation_id); } } + for source in &sources_removed { + refcnt_dec_connection(database_core, source.connection_id); + refcnt_dec_source_secret_ref(database_core, source)?; + } + for view in &views_removed { for dependent_relation_id in &view.dependent_relations { - database_core.decrease_ref_count(*dependent_relation_id); + database_core.decrease_relation_ref_count(*dependent_relation_id); } } for sink in &sinks_removed { - if let Some(connection_id) = sink.connection_id { - // TODO(siyuan): wait for yezizp to refactor ref cnt - database_core.decrease_ref_count(connection_id); - } + refcnt_dec_connection(database_core, sink.connection_id); for dependent_relation_id in &sink.dependent_relations { - database_core.decrease_ref_count(*dependent_relation_id); + database_core.decrease_relation_ref_count(*dependent_relation_id); } + refcnt_dec_sink_secret_ref(database_core, sink); } for subscription in &subscriptions_removed { - for dependent_relation_id in &subscription.dependent_relations { - database_core.decrease_ref_count(*dependent_relation_id); - } + database_core.decrease_relation_ref_count(subscription.dependent_table_id); } let version = self @@ -1780,7 +2033,6 @@ impl CatalogManager { .into_iter() .map(|id| id.into()) .chain(all_sink_ids.into_iter().map(|id| id.into())) - .chain(all_subscription_ids.into_iter().map(|id| id.into())) .chain(all_streaming_job_source_ids.into_iter().map(|id| id.into())) .collect_vec(); @@ -1873,7 +2125,7 @@ impl CatalogManager { } for subscription in database_mgr.subscriptions.values() { - if subscription.dependent_relations.contains(&relation_id) { + if subscription.dependent_table_id == relation_id { let mut subscription = subscription.clone(); subscription.definition = alter_relation_rename_refs(&subscription.definition, from, to); @@ -2395,7 +2647,6 @@ impl CatalogManager { alter_owner_request::Object::SubscriptionId(subscription_id) => { database_core.ensure_subscription_id(subscription_id)?; let mut subscriptions = BTreeMapTransaction::new(&mut database_core.subscriptions); - let mut tables = BTreeMapTransaction::new(&mut database_core.tables); let mut subscription = subscriptions.get_mut(subscription_id).unwrap(); let old_owner_id = subscription.owner; if old_owner_id == owner_id { @@ -2403,26 +2654,12 @@ impl CatalogManager { } subscription.owner = owner_id; - let mut relations = vec![Relation { + let relations = vec![Relation { relation_info: Some(RelationInfo::Subscription(subscription.clone())), }]; - // internal tables - let internal_table_ids = fragment_manager - .select_table_fragments_by_table_id(&(subscription_id.into())) - .await? - .internal_table_ids(); - for id in internal_table_ids { - let mut table = tables.get_mut(id).unwrap(); - assert_eq!(old_owner_id, table.owner); - table.owner = owner_id; - relations.push(Relation { - relation_info: Some(RelationInfo::Table(table.clone())), - }); - } - relation_info = Info::RelationGroup(RelationGroup { relations }); - commit_meta!(self, subscriptions, tables)?; + commit_meta!(self, subscriptions)?; user_core.increase_ref(owner_id); user_core.decrease_ref(old_owner_id); } @@ -2659,14 +2896,6 @@ impl CatalogManager { return Ok(IGNORED_NOTIFICATION_VERSION); } - // internal tables. - let to_update_internal_table_ids = Vec::from_iter( - fragment_manager - .select_table_fragments_by_table_id(&(subscription_id.into())) - .await? - .internal_table_ids(), - ); - database_core.check_relation_name_duplicated(&( database_id, new_schema_id, @@ -2676,15 +2905,7 @@ impl CatalogManager { let mut subscription = subscriptions.get_mut(subscription_id).unwrap(); subscription.schema_id = new_schema_id; relation_infos.push(Some(RelationInfo::Subscription(subscription.clone()))); - - let mut tables = BTreeMapTransaction::new(&mut database_core.tables); - for table_id in to_update_internal_table_ids { - let mut table = tables.get_mut(table_id).unwrap(); - table.schema_id = new_schema_id; - relation_infos.push(Some(RelationInfo::Table(table.clone()))); - } - - commit_meta!(self, subscriptions, tables)?; + commit_meta!(self, subscriptions)?; } } @@ -2769,6 +2990,7 @@ impl CatalogManager { } else { database_core.mark_creating(&key); user_core.increase_ref(source.owner); + refcnt_inc_source_secret_ref(database_core, source)?; // We have validate the status of connection before starting the procedure. refcnt_inc_connection(database_core, source.connection_id)?; Ok(()) @@ -2784,7 +3006,7 @@ impl CatalogManager { database_core .get_connection(connection_id) .cloned() - .ok_or_else(|| anyhow!(format!("could not find connection {}", connection_id)).into()) + .ok_or_else(|| MetaError::catalog_id_not_found("connection", connection_id)) } pub async fn finish_create_source_procedure( @@ -2844,6 +3066,7 @@ impl CatalogManager { database_core.unmark_creating(&key); user_core.decrease_ref(source.owner); refcnt_dec_connection(database_core, source.connection_id); + refcnt_dec_source_secret_ref(database_core, source)?; Ok(()) } @@ -2876,6 +3099,7 @@ impl CatalogManager { // source and table user_core.increase_ref_count(source.owner, 2); + refcnt_inc_source_secret_ref(database_core, source)?; // We have validate the status of connection before starting the procedure. refcnt_inc_connection(database_core, source.connection_id)?; Ok(()) @@ -2949,7 +3173,11 @@ impl CatalogManager { Ok(version) } - pub async fn cancel_create_table_procedure_with_source(&self, source: &Source, table: &Table) { + pub async fn cancel_create_table_procedure_with_source( + &self, + source: &Source, + table: &Table, + ) -> MetaResult<()> { let core = &mut *self.core.lock().await; let database_core = &mut core.database; let user_core = &mut core.user; @@ -2966,6 +3194,8 @@ impl CatalogManager { database_core.unmark_creating_streaming_job(table.id); user_core.decrease_ref_count(source.owner, 2); // source and table refcnt_dec_connection(database_core, source.connection_id); + refcnt_dec_source_secret_ref(database_core, source)?; + Ok(()) } pub async fn start_create_index_procedure( @@ -2995,7 +3225,7 @@ impl CatalogManager { database_core.mark_creating(&key); database_core.mark_creating_streaming_job(index_table.id, key); for &dependent_relation_id in &index_table.dependent_relations { - database_core.increase_ref_count(dependent_relation_id); + database_core.increase_relation_ref_count(dependent_relation_id); } // index table and index. user_core.increase_ref_count(index.owner, 2); @@ -3016,7 +3246,7 @@ impl CatalogManager { database_core.unmark_creating(&key); database_core.unmark_creating_streaming_job(index_table.id); for &dependent_relation_id in &index_table.dependent_relations { - database_core.decrease_ref_count(dependent_relation_id); + database_core.decrease_relation_ref_count(dependent_relation_id); } // index table and index. user_core.decrease_ref_count(index.owner, 2); @@ -3087,7 +3317,6 @@ impl CatalogManager { database_core.ensure_database_id(sink.database_id)?; database_core.ensure_schema_id(sink.schema_id)?; for dependent_id in &sink.dependent_relations { - // TODO(zehua): refactor when using SourceId. database_core.ensure_table_view_or_source_id(dependent_id)?; } let key = (sink.database_id, sink.schema_id, sink.name.clone()); @@ -3101,9 +3330,10 @@ impl CatalogManager { database_core.mark_creating(&key); database_core.mark_creating_streaming_job(sink.id, key); for &dependent_relation_id in &sink.dependent_relations { - database_core.increase_ref_count(dependent_relation_id); + database_core.increase_relation_ref_count(dependent_relation_id); } user_core.increase_ref(sink.owner); + refcnt_inc_sink_secret_ref(database_core, sink); // We have validate the status of connection before starting the procedure. refcnt_inc_connection(database_core, sink.connection_id)?; Ok(()) @@ -3175,10 +3405,11 @@ impl CatalogManager { database_core.unmark_creating(&key); database_core.unmark_creating_streaming_job(sink.id); for &dependent_relation_id in &sink.dependent_relations { - database_core.decrease_ref_count(dependent_relation_id); + database_core.decrease_relation_ref_count(dependent_relation_id); } user_core.decrease_ref(sink.owner); refcnt_dec_connection(database_core, sink.connection_id); + refcnt_dec_sink_secret_ref(database_core, sink); if let Some((table, source)) = target_table { Self::cancel_replace_table_procedure_inner(source, table, core); @@ -3194,9 +3425,8 @@ impl CatalogManager { let user_core = &mut core.user; database_core.ensure_database_id(subscription.database_id)?; database_core.ensure_schema_id(subscription.schema_id)?; - for dependent_id in &subscription.dependent_relations { - database_core.ensure_table_view_or_source_id(dependent_id)?; - } + database_core + .ensure_table_view_or_source_id(&TableId::from(subscription.dependent_table_id))?; let key = ( subscription.database_id, subscription.schema_id, @@ -3211,39 +3441,36 @@ impl CatalogManager { } else { database_core.mark_creating(&key); database_core.mark_creating_streaming_job(subscription.id, key); - for &dependent_relation_id in &subscription.dependent_relations { - database_core.increase_ref_count(dependent_relation_id); - } + database_core.increase_relation_ref_count(subscription.dependent_table_id); user_core.increase_ref(subscription.owner); + let mut subscriptions = BTreeMapTransaction::new(&mut database_core.subscriptions); + subscriptions.insert(subscription.id, subscription.clone()); + commit_meta!(self, subscriptions)?; Ok(()) } } pub async fn finish_create_subscription_procedure( &self, - mut internal_tables: Vec
, - mut subscription: Subscription, - ) -> MetaResult { + subscription_id: SubscriptionId, + ) -> MetaResult<()> { let core = &mut *self.core.lock().await; let database_core = &mut core.database; + let mut subscriptions = BTreeMapTransaction::new(&mut database_core.subscriptions); + let mut subscription = subscriptions + .get(&subscription_id) + .ok_or_else(|| MetaError::catalog_id_not_found("subscription", subscription_id))? + .clone(); + subscription.created_at_cluster_version = Some(current_cluster_version()); + subscription.created_at_epoch = Some(Epoch::now().0); let key = ( subscription.database_id, subscription.schema_id, subscription.name.clone(), ); - let log_store_names: Vec<_> = internal_tables - .iter() - .filter(|a| is_subscription_internal_table(&subscription.name, a.get_name())) - .map(|a| a.get_name()) - .collect(); - if log_store_names.len() != 1 { - bail!("A subscription can only have one log_store_name"); - } - subscription.subscription_internal_table_name = log_store_names.get(0).cloned().cloned(); - let mut tables = BTreeMapTransaction::new(&mut database_core.tables); - let mut subscriptions = BTreeMapTransaction::new(&mut database_core.subscriptions); + assert!( - !subscriptions.contains_key(&subscription.id) + subscription.subscription_state == Into::::into(PbSubscriptionState::Init) && database_core.in_progress_creation_tracker.contains(&key), "subscription must be in creating procedure" ); @@ -3253,13 +3480,28 @@ impl CatalogManager { .in_progress_creation_streaming_job .remove(&subscription.id); - subscription.stream_job_status = PbStreamJobStatus::Created.into(); + subscription.subscription_state = PbSubscriptionState::Created.into(); subscriptions.insert(subscription.id, subscription.clone()); - for table in &mut internal_tables { - table.stream_job_status = PbStreamJobStatus::Created.into(); - tables.insert(table.id, table.clone()); - } - commit_meta!(self, subscriptions, tables)?; + commit_meta!(self, subscriptions)?; + Ok(()) + } + + pub async fn notify_create_subscription( + &self, + subscription_id: SubscriptionId, + ) -> MetaResult { + let core = &mut *self.core.lock().await; + let database_core = &mut core.database; + let subscriptions = BTreeMapTransaction::new(&mut database_core.subscriptions); + let subscription = subscriptions + .get(&subscription_id) + .ok_or_else(|| MetaError::catalog_id_not_found("subscription", subscription_id))? + .clone(); + assert_eq!( + subscription.subscription_state, + Into::::into(PbSubscriptionState::Created) + ); + commit_meta!(self, subscriptions)?; let version = self .notify_frontend( @@ -3267,44 +3509,50 @@ impl CatalogManager { Info::RelationGroup(RelationGroup { relations: vec![Relation { relation_info: RelationInfo::Subscription(subscription.to_owned()).into(), - }] - .into_iter() - .chain(internal_tables.into_iter().map(|internal_table| Relation { - relation_info: RelationInfo::Table(internal_table).into(), - })) - .collect_vec(), + }], }), ) .await; - Ok(version) } - pub async fn cancel_create_subscription_procedure(&self, subscription: &Subscription) { + pub async fn clean_dirty_subscription(&self) -> MetaResult<()> { let core = &mut *self.core.lock().await; let database_core = &mut core.database; + let mut subscriptions = BTreeMapTransaction::new(&mut database_core.subscriptions); + let remove_subscriptions = subscriptions + .tree_ref() + .iter() + .filter(|(_, s)| s.subscription_state == Into::::into(PbSubscriptionState::Init)) + .map(|(_, s)| s.clone()) + .collect_vec(); let user_core = &mut core.user; - let key = ( - subscription.database_id, - subscription.schema_id, - subscription.name.clone(), - ); - assert!( - !database_core.subscriptions.contains_key(&subscription.id), - "subscription must be in creating procedure" - ); + for s in &remove_subscriptions { + subscriptions.remove(s.id); + } + commit_meta!(self, subscriptions)?; + for subscription in remove_subscriptions { + let key = ( + subscription.database_id, + subscription.schema_id, + subscription.name.clone(), + ); + assert!( + !database_core.subscriptions.contains_key(&subscription.id), + "subscription must be in creating procedure" + ); - database_core.unmark_creating(&key); - database_core.unmark_creating_streaming_job(subscription.id); - for &dependent_relation_id in &subscription.dependent_relations { - database_core.decrease_ref_count(dependent_relation_id); + database_core.unmark_creating(&key); + database_core.unmark_creating_streaming_job(subscription.id); + database_core.decrease_relation_ref_count(subscription.dependent_table_id); + user_core.decrease_ref(subscription.owner); } - user_core.decrease_ref(subscription.owner); + Ok(()) } /// This is used for `ALTER TABLE ADD/DROP COLUMN`. pub async fn start_replace_table_procedure(&self, stream_job: &StreamingJob) -> MetaResult<()> { - let StreamingJob::Table(source, table, ..) = stream_job else { + let StreamingJob::Table(source, table, job_type) = stream_job else { unreachable!("unexpected job: {stream_job:?}") }; let core = &mut *self.core.lock().await; @@ -3312,7 +3560,10 @@ impl CatalogManager { database_core.ensure_database_id(table.database_id)?; database_core.ensure_schema_id(table.schema_id)?; - assert!(table.dependent_relations.is_empty()); + // general table streaming job should not have dependent relations + if matches!(job_type, TableJobType::General) { + assert!(table.dependent_relations.is_empty()); + } let key = (table.database_id, table.schema_id, table.name.clone()); let original_table = database_core @@ -3467,8 +3718,6 @@ impl CatalogManager { let database_core = &mut core.database; let key = (table.database_id, table.schema_id, table.name.clone()); - assert!(table.dependent_relations.is_empty()); - assert!( database_core.tables.contains_key(&table.id) && database_core.has_in_progress_creation(&key), @@ -3539,6 +3788,10 @@ impl CatalogManager { self.core.lock().await.database.list_databases() } + pub async fn list_schemas(&self) -> Vec { + self.core.lock().await.database.list_schemas() + } + pub async fn list_tables(&self) -> Vec
{ self.core.lock().await.database.list_tables() } @@ -3628,6 +3881,10 @@ impl CatalogManager { .list_dml_table_ids(schema_id) } + pub async fn list_view_ids(&self, schema_id: SchemaId) -> Vec { + self.core.lock().await.database.list_view_ids(schema_id) + } + pub async fn list_sources(&self) -> Vec { self.core.lock().await.database.list_sources() } @@ -3718,6 +3975,13 @@ impl CatalogManager { } } + for subscription in core.subscriptions.values() { + dependencies.push(PbObjectDependencies { + object_id: subscription.id, + referenced_object_id: subscription.dependent_table_id, + }); + } + dependencies } @@ -3761,6 +4025,19 @@ impl CatalogManager { tables } + pub async fn get_subscription_by_id( + &self, + subscription_id: SubscriptionId, + ) -> MetaResult { + let guard = self.core.lock().await; + let subscription = guard + .database + .subscriptions + .get(&subscription_id) + .ok_or_else(|| MetaError::catalog_id_not_found("subscription", subscription_id))?; + Ok(subscription.clone()) + } + pub async fn get_created_table_ids(&self) -> Vec { let guard = self.core.lock().await; guard @@ -3877,7 +4154,6 @@ impl CatalogManager { Ok(()) } - #[cfg(test)] pub async fn list_users(&self) -> Vec { self.core.lock().await.user.list_users() } @@ -3947,7 +4223,7 @@ impl CatalogManager { core.user_info .get(&id) .cloned() - .ok_or_else(|| anyhow!("User {} not found", id).into()) + .ok_or_else(|| MetaError::catalog_id_not_found("user", id)) } pub async fn drop_user(&self, id: UserId) -> MetaResult { @@ -3966,7 +4242,7 @@ impl CatalogManager { id ))); } - if user_core.catalog_create_ref_count.get(&id).is_some() { + if user_core.catalog_create_ref_count.contains_key(&id) { return Err(MetaError::permission_denied(format!( "User {} cannot be dropped because some objects depend on it", user.name @@ -4077,11 +4353,11 @@ impl CatalogManager { let grantor_info = users .get(&grantor) .cloned() - .ok_or_else(|| anyhow!("User {} does not exist", &grantor))?; + .ok_or_else(|| MetaError::catalog_id_not_found("user", grantor))?; for user_id in user_ids { let mut user = users .get_mut(*user_id) - .ok_or_else(|| anyhow!("User {} does not exist", user_id))?; + .ok_or_else(|| MetaError::catalog_id_not_found("user", user_id))?; if user.is_super { return Err(MetaError::permission_denied(format!( @@ -4204,7 +4480,7 @@ impl CatalogManager { // check revoke permission let revoke_by = users .get(&revoke_by) - .ok_or_else(|| anyhow!("User {} does not exist", &revoke_by))?; + .ok_or_else(|| MetaError::catalog_id_not_found("user", revoke_by))?; let same_user = granted_by == revoke_by.id; if !revoke_by.is_super { for privilege in revoke_grant_privileges { @@ -4239,7 +4515,7 @@ impl CatalogManager { let user = users .get(user_id) .cloned() - .ok_or_else(|| anyhow!("User {} does not exist", user_id))?; + .ok_or_else(|| MetaError::catalog_id_not_found("user", user_id))?; if user.is_super { return Err(MetaError::permission_denied(format!( "Cannot revoke privilege from supper user {}", diff --git a/src/meta/src/manager/catalog/user.rs b/src/meta/src/manager/catalog/user.rs index 0da37291a1634..f6e2f9e03e835 100644 --- a/src/meta/src/manager/catalog/user.rs +++ b/src/meta/src/manager/catalog/user.rs @@ -67,6 +67,13 @@ impl UserManager { .map(|table| table.owner), ) .chain(database.views.values().map(|view| view.owner)) + .chain(database.functions.values().map(|function| function.owner)) + .chain( + database + .connections + .values() + .map(|connection| connection.owner), + ) .for_each(|owner_id| user_manager.increase_ref(owner_id)); Ok(user_manager) @@ -148,7 +155,7 @@ mod tests { use super::*; use crate::manager::{commit_meta, CatalogManager}; - use crate::model::{BTreeMapTransaction, ValTransaction}; + use crate::model::BTreeMapTransaction; use crate::storage::Transaction; fn make_test_user(id: u32, name: &str) -> UserInfo { @@ -209,14 +216,12 @@ mod tests { owner: DEFAULT_SUPER_USER_ID, ..Default::default() }; - catalog_manager - .start_create_table_procedure(&table, vec![]) - .await?; + catalog_manager.start_create_table_procedure(&table).await?; catalog_manager .finish_create_table_procedure(vec![], table) .await?; catalog_manager - .start_create_table_procedure(&other_table, vec![]) + .start_create_table_procedure(&other_table) .await?; catalog_manager .finish_create_table_procedure(vec![], other_table) diff --git a/src/meta/src/manager/catalog/utils.rs b/src/meta/src/manager/catalog/utils.rs index 6c73e6eeebb39..49d286230cb7d 100644 --- a/src/meta/src/manager/catalog/utils.rs +++ b/src/meta/src/manager/catalog/utils.rs @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; + use risingwave_common::bail; +use risingwave_pb::catalog::{Sink, Source}; use crate::manager::{ConnectionId, DatabaseManager}; +use crate::MetaResult; pub fn refcnt_inc_connection( database_mgr: &mut DatabaseManager, @@ -22,8 +26,7 @@ pub fn refcnt_inc_connection( ) -> anyhow::Result<()> { if let Some(connection_id) = connection_id { if let Some(_conn) = database_mgr.get_connection(connection_id) { - // TODO(weili): wait for yezizp to refactor ref cnt - database_mgr.increase_ref_count(connection_id); + database_mgr.increase_connection_ref_count(connection_id); } else { bail!("connection {} not found.", connection_id); } @@ -36,7 +39,64 @@ pub fn refcnt_dec_connection( connection_id: Option, ) { if let Some(connection_id) = connection_id { - // TODO: wait for yezizp to refactor ref cnt - database_mgr.decrease_ref_count(connection_id); + database_mgr.decrease_connection_ref_count(connection_id); + } +} + +pub fn get_refed_secret_ids_from_source(source: &Source) -> MetaResult> { + let mut secret_ids = HashSet::new(); + for secret_ref in source.get_secret_refs().values() { + secret_ids.insert(secret_ref.secret_id); + } + // `info` must exist in `Source` + for secret_ref in source.get_info()?.get_format_encode_secret_refs().values() { + secret_ids.insert(secret_ref.secret_id); + } + Ok(secret_ids) +} + +pub fn get_refed_secret_ids_from_sink(sink: &Sink) -> HashSet { + let mut secret_ids = HashSet::new(); + for secret_ref in sink.get_secret_refs().values() { + secret_ids.insert(secret_ref.secret_id); + } + // `format_desc` may not exist in `Sink` + if let Some(format_desc) = &sink.format_desc { + for secret_ref in format_desc.get_secret_refs().values() { + secret_ids.insert(secret_ref.secret_id); + } + } + secret_ids +} + +pub fn refcnt_inc_source_secret_ref( + database_mgr: &mut DatabaseManager, + source: &Source, +) -> MetaResult<()> { + for secret_id in get_refed_secret_ids_from_source(source)? { + database_mgr.increase_secret_ref_count(secret_id); + } + Ok(()) +} + +pub fn refcnt_dec_source_secret_ref( + database_mgr: &mut DatabaseManager, + source: &Source, +) -> MetaResult<()> { + for secret_id in get_refed_secret_ids_from_source(source)? { + database_mgr.decrease_secret_ref_count(secret_id); + } + Ok(()) +} + +pub fn refcnt_inc_sink_secret_ref(database_mgr: &mut DatabaseManager, sink: &Sink) { + for secret_id in get_refed_secret_ids_from_sink(sink) { + database_mgr.increase_secret_ref_count(secret_id); + } +} + +pub fn refcnt_dec_sink_secret_ref(database_mgr: &mut DatabaseManager, sink: &Sink) { + for secret_id in get_refed_secret_ids_from_sink(sink) { + database_mgr.decrease_secret_ref_count(secret_id); } } diff --git a/src/meta/src/manager/cluster.rs b/src/meta/src/manager/cluster.rs index 0aeb610b7ce91..a5e2c8175ea6b 100644 --- a/src/meta/src/manager/cluster.rs +++ b/src/meta/src/manager/cluster.rs @@ -38,7 +38,9 @@ use tokio::sync::{RwLock, RwLockReadGuard}; use tokio::task::JoinHandle; use crate::manager::{IdCategory, LocalNotification, MetaSrvEnv}; -use crate::model::{MetadataModel, ValTransaction, VarTransaction, Worker, INVALID_EXPIRE_AT}; +use crate::model::{ + InMemValTransaction, MetadataModel, ValTransaction, VarTransaction, Worker, INVALID_EXPIRE_AT, +}; use crate::storage::{MetaStore, Transaction}; use crate::{MetaError, MetaResult}; @@ -125,7 +127,7 @@ impl ClusterManager { .unwrap_or_default(); } - let old_worker_parallelism = worker.worker_node.parallel_units.len(); + let old_worker_parallelism = worker.worker_node.parallelism(); if old_worker_parallelism == new_worker_parallelism && worker.worker_node.property == property { @@ -319,7 +321,7 @@ impl ClusterManager { Ok(()) } - pub async fn delete_worker_node(&self, host_address: HostAddress) -> MetaResult { + pub async fn delete_worker_node(&self, host_address: HostAddress) -> MetaResult { let mut core = self.core.write().await; let worker = core.get_worker_by_host_checked(host_address.clone())?; let worker_type = worker.worker_type(); @@ -344,10 +346,10 @@ impl ClusterManager { // local notification. self.env .notification_manager() - .notify_local_subscribers(LocalNotification::WorkerNodeDeleted(worker_node)) + .notify_local_subscribers(LocalNotification::WorkerNodeDeleted(worker_node.clone())) .await; - Ok(worker_type) + Ok(worker_node) } /// Invoked when it receives a heartbeat from a worker node. @@ -410,7 +412,8 @@ impl ClusterManager { // 3. Delete expired workers. for (worker_id, key) in workers_to_delete { match cluster_manager.delete_worker_node(key.clone()).await { - Ok(worker_type) => { + Ok(worker_node) => { + let worker_type = worker_node.r#type(); match worker_type { WorkerType::Frontend | WorkerType::ComputeNode @@ -551,6 +554,13 @@ pub struct StreamingClusterInfo { pub unschedulable_parallel_units: HashMap, } +// Encapsulating the use of parallel_units. +impl StreamingClusterInfo { + pub fn parallelism(&self) -> usize { + self.parallel_units.len() + } +} + pub struct ClusterManagerCore { env: MetaSrvEnv, /// Record for workers in the cluster. @@ -566,7 +576,7 @@ impl ClusterManagerCore { pub const MAX_WORKER_REUSABLE_ID_COUNT: usize = 1 << Self::MAX_WORKER_REUSABLE_ID_BITS; async fn new(env: MetaSrvEnv) -> MetaResult { - let meta_store = env.meta_store().as_kv(); + let meta_store = env.meta_store_ref().as_kv(); let mut workers = Worker::list(meta_store).await?; let used_transactional_ids: HashSet<_> = workers @@ -882,7 +892,7 @@ mod tests { ) .await .unwrap(); - assert_eq!(worker_node.parallel_units.len(), fake_parallelism + 4); + assert_eq!(worker_node.parallelism(), fake_parallelism + 4); assert_cluster_manager(&cluster_manager, parallel_count + 4).await; // re-register existing worker node with smaller parallelism. @@ -906,11 +916,11 @@ mod tests { .unwrap(); if !env.opts.disable_automatic_parallelism_control { - assert_eq!(worker_node.parallel_units.len(), fake_parallelism - 2); + assert_eq!(worker_node.parallelism(), fake_parallelism - 2); assert_cluster_manager(&cluster_manager, parallel_count - 2).await; } else { // compatibility mode - assert_eq!(worker_node.parallel_units.len(), fake_parallelism + 4); + assert_eq!(worker_node.parallelism(), fake_parallelism + 4); assert_cluster_manager(&cluster_manager, parallel_count + 4).await; } diff --git a/src/meta/src/manager/diagnose.rs b/src/meta/src/manager/diagnose.rs index fbc04f9324002..7d5d33b57b198 100644 --- a/src/meta/src/manager/diagnose.rs +++ b/src/meta/src/manager/diagnose.rs @@ -20,6 +20,7 @@ use std::sync::Arc; use itertools::Itertools; use prometheus_http_query::response::Data::Vector; use risingwave_common::types::Timestamptz; +use risingwave_common::util::StackTraceResponseExt; use risingwave_pb::common::WorkerType; use risingwave_pb::hummock::Level; use risingwave_pb::meta::event_log::Event; @@ -213,7 +214,7 @@ impl DiagnoseCommand { &mut row, worker_node.get_state().ok().map(|s| s.as_str_name()), ); - row.add_cell(worker_node.parallel_units.len().into()); + row.add_cell(worker_node.parallelism().into()); try_add_cell( &mut row, worker_node.property.as_ref().map(|p| p.is_streaming), @@ -664,53 +665,18 @@ impl DiagnoseCommand { return; }; - let mut all = Default::default(); - - fn merge(a: &mut StackTraceResponse, b: StackTraceResponse) { - a.actor_traces.extend(b.actor_traces); - a.rpc_traces.extend(b.rpc_traces); - a.compaction_task_traces.extend(b.compaction_task_traces); - a.inflight_barrier_traces.extend(b.inflight_barrier_traces); - } + let mut all = StackTraceResponse::default(); let compute_clients = ComputeClientPool::default(); for worker_node in &worker_nodes { if let Ok(client) = compute_clients.get(worker_node).await && let Ok(result) = client.stack_trace().await { - merge(&mut all, result); + all.merge_other(result); } } - if !all.actor_traces.is_empty() { - let _ = writeln!(s, "--- Actor Traces ---"); - for (actor_id, trace) in &all.actor_traces { - let _ = writeln!(s, ">> Actor {}", *actor_id); - let _ = writeln!(s, "{trace}"); - } - } - if !all.rpc_traces.is_empty() { - let _ = writeln!(s, "--- RPC Traces ---"); - for (name, trace) in &all.rpc_traces { - let _ = writeln!(s, ">> RPC {name}"); - let _ = writeln!(s, "{trace}"); - } - } - if !all.compaction_task_traces.is_empty() { - let _ = writeln!(s, "--- Compactor Traces ---"); - for (name, trace) in &all.compaction_task_traces { - let _ = writeln!(s, ">> Compaction Task {name}"); - let _ = writeln!(s, "{trace}"); - } - } - - if !all.inflight_barrier_traces.is_empty() { - let _ = writeln!(s, "--- Inflight Barrier Traces ---"); - for (name, trace) in &all.inflight_barrier_traces { - let _ = writeln!(s, ">> Barrier {name}"); - let _ = writeln!(s, "{trace}"); - } - } + write!(s, "{}", all.output()).unwrap(); } } diff --git a/src/meta/src/manager/env.rs b/src/meta/src/manager/env.rs index ca99f5de03dea..e10d64a656328 100644 --- a/src/meta/src/manager/env.rs +++ b/src/meta/src/manager/env.rs @@ -20,6 +20,7 @@ use risingwave_common::config::{ }; use risingwave_common::session_config::SessionConfig; use risingwave_common::system_param::reader::SystemParamsReader; +use risingwave_meta_model_migration::{MigrationStatus, Migrator, MigratorTrait}; use risingwave_meta_model_v2::prelude::Cluster; use risingwave_pb::meta::SystemParams; use risingwave_rpc_client::{StreamClientPool, StreamClientPoolRef}; @@ -188,7 +189,11 @@ pub struct MetaOpts { /// Interval of reporting the number of nodes in the cluster. pub node_num_monitor_interval_sec: u64, - /// The Prometheus endpoint for dashboard service. + /// The Prometheus endpoint for Meta Dashboard Service. + /// The Dashboard service uses this in the following ways: + /// 1. Query Prometheus for relevant metrics to find Stream Graph Bottleneck, and display it. + /// 2. Provide cluster diagnostics, at `/api/monitor/diagnose` to troubleshoot cluster. + /// These are just examples which show how the Meta Dashboard Service queries Prometheus. pub prometheus_endpoint: Option, /// The additional selector used when querying Prometheus. @@ -243,12 +248,12 @@ pub struct MetaOpts { /// hybird compaction group config /// - /// `hybird_partition_vnode_count` determines the granularity of vnodes in the hybrid compaction group for SST alignment. - /// When `hybird_partition_vnode_count` > 0, in hybrid compaction group + /// `hybrid_partition_vnode_count` determines the granularity of vnodes in the hybrid compaction group for SST alignment. + /// When `hybrid_partition_vnode_count` > 0, in hybrid compaction group /// - Tables with high write throughput will be split at vnode granularity /// - Tables with high size tables will be split by table granularity - /// When `hybird_partition_vnode_count` = 0,no longer be special alignment operations for the hybird compaction group - pub hybird_partition_vnode_count: u32, + /// When `hybrid_partition_vnode_count` = 0,no longer be special alignment operations for the hybird compaction group + pub hybrid_partition_node_count: u32, pub event_log_enabled: bool, pub event_log_channel_max_size: u32, @@ -267,6 +272,20 @@ pub struct MetaOpts { pub enable_check_task_level_overlap: bool, pub enable_dropped_column_reclaim: bool, pub object_store_config: ObjectStoreConfig, + + /// The maximum number of trivial move tasks to be picked in a single loop + pub max_trivial_move_task_count_per_loop: usize, + + /// The maximum number of times to probe for `PullTaskEvent` + pub max_get_task_probe_times: usize, + + pub compact_task_table_size_partition_threshold_low: u64, + pub compact_task_table_size_partition_threshold_high: u64, + + // The private key for the secret store, used when the secret is stored in the meta. + pub secret_store_private_key: Vec, + + pub table_info_statistic_history_times: usize, } impl MetaOpts { @@ -305,6 +324,8 @@ impl MetaOpts { periodic_split_compact_group_interval_sec: 60, split_group_size_limit: 5 * 1024 * 1024 * 1024, min_table_split_size: 2 * 1024 * 1024 * 1024, + compact_task_table_size_partition_threshold_low: 128 * 1024 * 1024, + compact_task_table_size_partition_threshold_high: 512 * 1024 * 1024, table_write_throughput_threshold: 128 * 1024 * 1024, min_table_split_write_throughput: 64 * 1024 * 1024, do_not_config_object_storage_lifecycle: true, @@ -313,7 +334,7 @@ impl MetaOpts { compaction_task_max_progress_interval_secs: 1, compaction_config: None, cut_table_size_limit: 1024 * 1024 * 1024, - hybird_partition_vnode_count: 4, + hybrid_partition_node_count: 4, event_log_enabled: false, event_log_channel_max_size: 1, advertise_addr: "".to_string(), @@ -323,19 +344,40 @@ impl MetaOpts { enable_check_task_level_overlap: true, enable_dropped_column_reclaim: false, object_store_config: ObjectStoreConfig::default(), + max_trivial_move_task_count_per_loop: 256, + max_get_task_probe_times: 5, + secret_store_private_key: "demo-secret-private-key".as_bytes().to_vec(), + table_info_statistic_history_times: 240, } } } +/// This function `is_first_launch_for_sql_backend_cluster` is used to check whether the cluster, which uses SQL as the backend, is a new cluster. +/// It determines this by inspecting the applied migrations. If the migration `m20230908_072257_init` has been applied, +/// then it is considered an old cluster. +/// +/// Note: this check should be performed before `Migrator::up()`. +pub async fn is_first_launch_for_sql_backend_cluster( + sql_meta_store: &SqlMetaStore, +) -> MetaResult { + let migrations = Migrator::get_applied_migrations(&sql_meta_store.conn).await?; + for migration in migrations { + if migration.name() == "m20230908_072257_init" + && migration.status() == MigrationStatus::Applied + { + return Ok(false); + } + } + Ok(true) +} + impl MetaSrvEnv { pub async fn new( opts: MetaOpts, - init_system_params: SystemParams, + mut init_system_params: SystemParams, init_session_config: SessionConfig, meta_store_impl: MetaStoreImpl, ) -> MetaResult { - let notification_manager = - Arc::new(NotificationManager::new(meta_store_impl.clone()).await); let idle_manager = Arc::new(IdleManager::new(opts.max_idle_ms)); let stream_client_pool = Arc::new(StreamClientPool::default()); let event_log_manager = Arc::new(start_event_log_manager( @@ -345,6 +387,8 @@ impl MetaSrvEnv { let env = match &meta_store_impl { MetaStoreImpl::Kv(meta_store) => { + let notification_manager = + Arc::new(NotificationManager::new(meta_store_impl.clone()).await); let id_gen_manager = Arc::new(IdGeneratorManager::new(meta_store.clone()).await); let (cluster_id, cluster_first_launch) = if let Some(id) = ClusterId::from_meta_store(meta_store).await? { @@ -352,6 +396,11 @@ impl MetaSrvEnv { } else { (ClusterId::new(), true) }; + + // For new clusters, the name of the object store needs to be prefixed according to the object id. + // For old clusters, the prefix is ​​not divided for the sake of compatibility. + + init_system_params.use_new_object_prefix_strategy = Some(cluster_first_launch); let system_params_manager = Arc::new( SystemParamsManager::new( meta_store.clone(), @@ -394,11 +443,24 @@ impl MetaSrvEnv { } } MetaStoreImpl::Sql(sql_meta_store) => { + let is_sql_backend_cluster_first_launch = + is_first_launch_for_sql_backend_cluster(sql_meta_store).await?; + // Try to upgrade if any new model changes are added. + Migrator::up(&sql_meta_store.conn, None) + .await + .expect("Failed to upgrade models in meta store"); + + let notification_manager = + Arc::new(NotificationManager::new(meta_store_impl.clone()).await); let cluster_id = Cluster::find() .one(&sql_meta_store.conn) .await? .map(|c| c.cluster_id.to_string().into()) .unwrap(); + init_system_params.use_new_object_prefix_strategy = + Some(is_sql_backend_cluster_first_launch); + // For new clusters, the name of the object store needs to be prefixed according to the object id. + // For old clusters, the prefix is ​​not divided for the sake of compatibility. let system_param_controller = Arc::new( SystemParamsController::new( sql_meta_store.clone(), @@ -441,11 +503,11 @@ impl MetaSrvEnv { Ok(env) } - pub fn meta_store_ref(&self) -> MetaStoreImpl { + pub fn meta_store(&self) -> MetaStoreImpl { self.meta_store_impl.clone() } - pub fn meta_store(&self) -> &MetaStoreImpl { + pub fn meta_store_ref(&self) -> &MetaStoreImpl { &self.meta_store_impl } diff --git a/src/meta/src/manager/id.rs b/src/meta/src/manager/id.rs index 7fbde6d655e83..023483116fdc8 100644 --- a/src/meta/src/manager/id.rs +++ b/src/meta/src/manager/id.rs @@ -137,6 +137,8 @@ pub mod IdCategory { pub const CompactionGroup: IdCategoryType = 15; pub const Function: IdCategoryType = 16; pub const Connection: IdCategoryType = 17; + + pub const Secret: IdCategoryType = 18; } pub type IdGeneratorManagerRef = Arc; @@ -160,6 +162,7 @@ pub struct IdGeneratorManager { parallel_unit: Arc, compaction_group: Arc, connection: Arc, + secret: Arc, } impl IdGeneratorManager { @@ -209,6 +212,7 @@ impl IdGeneratorManager { connection: Arc::new( StoredIdGenerator::new(meta_store.clone(), "connection", None).await, ), + secret: Arc::new(StoredIdGenerator::new(meta_store.clone(), "secret", None).await), } } @@ -230,6 +234,7 @@ impl IdGeneratorManager { IdCategory::HummockCompactionTask => &self.hummock_compaction_task, IdCategory::CompactionGroup => &self.compaction_group, IdCategory::Connection => &self.connection, + IdCategory::Secret => &self.secret, _ => unreachable!(), } } diff --git a/src/meta/src/manager/metadata.rs b/src/meta/src/manager/metadata.rs index 876f5c3365d36..241a47941755b 100644 --- a/src/meta/src/manager/metadata.rs +++ b/src/meta/src/manager/metadata.rs @@ -24,7 +24,8 @@ use risingwave_pb::common::worker_node::{PbResource, State}; use risingwave_pb::common::{HostAddress, PbWorkerNode, PbWorkerType, WorkerNode, WorkerType}; use risingwave_pb::meta::add_worker_node_request::Property as AddNodeProperty; use risingwave_pb::meta::table_fragments::{ActorStatus, Fragment, PbFragment}; -use risingwave_pb::stream_plan::{PbDispatchStrategy, PbStreamActor, StreamActor}; +use risingwave_pb::stream_plan::{PbDispatchStrategy, StreamActor}; +use risingwave_pb::stream_service::BuildActorInfo; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; use tokio::time::sleep; use tracing::warn; @@ -37,7 +38,7 @@ use crate::manager::{ StreamingClusterInfo, WorkerId, }; use crate::model::{ActorId, FragmentId, MetadataModel, TableFragments, TableParallelism}; -use crate::stream::SplitAssignment; +use crate::stream::{to_build_actor_info, SplitAssignment}; use crate::telemetry::MetaTelemetryJobDesc; use crate::MetaResult; @@ -63,6 +64,7 @@ pub struct MetadataManagerV2 { #[derive(Debug)] pub(crate) enum ActiveStreamingWorkerChange { Add(WorkerNode), + #[expect(dead_code)] Remove(WorkerNode), Update(WorkerNode), } @@ -692,19 +694,26 @@ impl MetadataManager { pub async fn all_node_actors( &self, include_inactive: bool, - ) -> MetaResult>> { + ) -> MetaResult>> { + let subscriptions = self.get_mv_depended_subscriptions().await?; match &self { - MetadataManager::V1(mgr) => { - Ok(mgr.fragment_manager.all_node_actors(include_inactive).await) - } + MetadataManager::V1(mgr) => Ok(mgr + .fragment_manager + .all_node_actors(include_inactive, &subscriptions) + .await), MetadataManager::V2(mgr) => { let table_fragments = mgr.catalog_controller.table_fragments().await?; let mut actor_maps = HashMap::new(); for (_, fragments) in table_fragments { let tf = TableFragments::from_protobuf(fragments); - for (node_id, actor_ids) in tf.worker_actors(include_inactive) { - let node_actor_ids = actor_maps.entry(node_id).or_insert_with(Vec::new); - node_actor_ids.extend(actor_ids); + let table_id = tf.table_id(); + for (node_id, actors) in tf.worker_actors(include_inactive) { + let node_actors = actor_maps.entry(node_id).or_insert_with(Vec::new); + node_actors.extend( + actors + .into_iter() + .map(|actor| to_build_actor_info(actor, &subscriptions, table_id)), + ) } } Ok(actor_maps) @@ -812,4 +821,12 @@ impl MetadataManager { } } } + + #[expect(clippy::unused_async)] + pub async fn get_mv_depended_subscriptions( + &self, + ) -> MetaResult>> { + // TODO(subscription): support the correct logic when supporting L0 log store subscriptions + Ok(HashMap::new()) + } } diff --git a/src/meta/src/manager/notification.rs b/src/meta/src/manager/notification.rs index 0ce47608cdfd2..dcf537ad6c7e7 100644 --- a/src/meta/src/manager/notification.rs +++ b/src/meta/src/manager/notification.rs @@ -101,7 +101,6 @@ impl NotificationManager { info: Some(task.info), version: task.version.unwrap_or_default(), }; - core.lock().await.notify(task.target, response); } }); diff --git a/src/meta/src/manager/session_params.rs b/src/meta/src/manager/session_params.rs index 662eae162a92d..e1f0afb21448c 100644 --- a/src/meta/src/manager/session_params.rs +++ b/src/meta/src/manager/session_params.rs @@ -22,7 +22,7 @@ use thiserror_ext::AsReport; use tokio::sync::RwLock; use tracing::info; -use crate::model::{ValTransaction, VarTransaction}; +use crate::model::{InMemValTransaction, ValTransaction, VarTransaction}; use crate::storage::{MetaStore, MetaStoreRef, Snapshot, Transaction}; use crate::MetaResult; diff --git a/src/meta/src/manager/sink_coordination/coordinator_worker.rs b/src/meta/src/manager/sink_coordination/coordinator_worker.rs index 3ea9e8819214f..679a7479419e3 100644 --- a/src/meta/src/manager/sink_coordination/coordinator_worker.rs +++ b/src/meta/src/manager/sink_coordination/coordinator_worker.rs @@ -19,7 +19,7 @@ use anyhow::anyhow; use futures::future::{select, Either}; use futures::stream::FuturesUnordered; use futures::{StreamExt, TryStreamExt}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::hash::{VirtualNode, VnodeBitmapExt}; use risingwave_connector::dispatch_sink; use risingwave_connector::sink::{build_sink, Sink, SinkCommitCoordinator, SinkParam}; diff --git a/src/meta/src/manager/sink_coordination/manager.rs b/src/meta/src/manager/sink_coordination/manager.rs index a9327b4613a35..fd2b986be28e7 100644 --- a/src/meta/src/manager/sink_coordination/manager.rs +++ b/src/meta/src/manager/sink_coordination/manager.rs @@ -19,7 +19,7 @@ use std::pin::pin; use futures::future::{select, BoxFuture, Either}; use futures::stream::FuturesUnordered; use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_connector::sink::catalog::SinkId; use risingwave_connector::sink::SinkParam; use risingwave_pb::connector_service::coordinate_request::Msg; @@ -357,7 +357,7 @@ mod tests { use futures::{FutureExt, StreamExt}; use itertools::Itertools; use rand::seq::SliceRandom; - use risingwave_common::buffer::{Bitmap, BitmapBuilder}; + use risingwave_common::bitmap::{Bitmap, BitmapBuilder}; use risingwave_common::hash::VirtualNode; use risingwave_connector::sink::catalog::{SinkId, SinkType}; use risingwave_connector::sink::{SinkCommitCoordinator, SinkError, SinkParam}; diff --git a/src/meta/src/manager/sink_coordination/mod.rs b/src/meta/src/manager/sink_coordination/mod.rs index 68f3f44624e38..ab44965891d5f 100644 --- a/src/meta/src/manager/sink_coordination/mod.rs +++ b/src/meta/src/manager/sink_coordination/mod.rs @@ -17,7 +17,7 @@ mod manager; use futures::stream::BoxStream; pub use manager::SinkCoordinatorManager; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_connector::sink::SinkParam; use risingwave_pb::connector_service::{CoordinateRequest, CoordinateResponse}; use tokio::sync::mpsc::Sender; diff --git a/src/meta/src/manager/streaming_job.rs b/src/meta/src/manager/streaming_job.rs index a29e6e923de2b..90d0781174b5d 100644 --- a/src/meta/src/manager/streaming_job.rs +++ b/src/meta/src/manager/streaming_job.rs @@ -12,21 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::collections::HashSet; + use risingwave_common::catalog::TableVersionId; use risingwave_common::current_cluster_version; use risingwave_common::util::epoch::Epoch; -use risingwave_pb::catalog::{CreateType, Index, PbSource, Sink, Subscription, Table}; +use risingwave_pb::catalog::{CreateType, Index, PbSource, Sink, Table}; use risingwave_pb::ddl_service::TableJobType; use strum::EnumDiscriminants; +use super::{get_refed_secret_ids_from_sink, get_refed_secret_ids_from_source}; use crate::model::FragmentId; +use crate::MetaResult; // This enum is used in order to re-use code in `DdlServiceImpl` for creating MaterializedView and // Sink. #[derive(Debug, Clone, EnumDiscriminants)] pub enum StreamingJob { MaterializedView(Table), - Subscription(Subscription), Sink(Sink, Option<(Table, Option)>), Table(Option, Table, TableJobType), Index(Index, Table), @@ -37,7 +40,6 @@ pub enum StreamingJob { pub enum DdlType { MaterializedView, Sink, - Subscription, Table(TableJobType), Index, Source, @@ -51,7 +53,6 @@ impl From<&StreamingJob> for DdlType { StreamingJob::Table(_, _, ty) => DdlType::Table(*ty), StreamingJob::Index(_, _) => DdlType::Index, StreamingJob::Source(_) => DdlType::Source, - StreamingJob::Subscription(_) => DdlType::Subscription, } } } @@ -94,10 +95,6 @@ impl StreamingJob { source.created_at_epoch = created_at_epoch; source.created_at_cluster_version = created_at_cluster_version; } - StreamingJob::Subscription(subscription) => { - subscription.created_at_epoch = created_at_epoch; - subscription.created_at_cluster_version = created_at_cluster_version; - } } } @@ -132,10 +129,6 @@ impl StreamingJob { source.initialized_at_epoch = initialized_at_epoch; source.initialized_at_cluster_version = initialized_at_cluster_version; } - StreamingJob::Subscription(subscription) => { - subscription.initialized_at_epoch = initialized_at_epoch; - subscription.initialized_at_cluster_version = initialized_at_cluster_version; - } } } } @@ -154,9 +147,6 @@ impl StreamingJob { StreamingJob::Source(src) => { src.id = id; } - StreamingJob::Subscription(subscription) => { - subscription.id = id; - } } } @@ -166,7 +156,7 @@ impl StreamingJob { Self::MaterializedView(table) | Self::Index(_, table) | Self::Table(_, table, ..) => { table.fragment_id = id; } - Self::Sink(_, _) | Self::Source(_) | Self::Subscription(_) => {} + Self::Sink(_, _) | Self::Source(_) => {} } } @@ -176,10 +166,7 @@ impl StreamingJob { Self::Table(_, table, ..) => { table.dml_fragment_id = id; } - Self::MaterializedView(_) - | Self::Index(_, _) - | Self::Sink(_, _) - | Self::Subscription(_) => {} + Self::MaterializedView(_) | Self::Index(_, _) | Self::Sink(_, _) => {} Self::Source(_) => {} } } @@ -191,7 +178,6 @@ impl StreamingJob { Self::Table(_, table, ..) => table.id, Self::Index(index, _) => index.id, Self::Source(source) => source.id, - Self::Subscription(subscription) => subscription.id, } } @@ -202,7 +188,6 @@ impl StreamingJob { Self::Table(_, table, ..) => Some(table.id), Self::Index(_, table) => Some(table.id), Self::Source(_) => None, - Self::Subscription(_) => None, } } @@ -212,7 +197,7 @@ impl StreamingJob { Self::MaterializedView(table) | Self::Index(_, table) | Self::Table(_, table, ..) => { Some(table) } - Self::Sink(_, _) | Self::Source(_) | Self::Subscription(_) => None, + Self::Sink(_, _) | Self::Source(_) => None, } } @@ -223,7 +208,6 @@ impl StreamingJob { Self::Table(_, table, ..) => table.schema_id, Self::Index(index, _) => index.schema_id, Self::Source(source) => source.schema_id, - Self::Subscription(subscription) => subscription.schema_id, } } @@ -234,7 +218,6 @@ impl StreamingJob { Self::Table(_, table, ..) => table.database_id, Self::Index(index, _) => index.database_id, Self::Source(source) => source.database_id, - Self::Subscription(subscription) => subscription.database_id, } } @@ -245,7 +228,6 @@ impl StreamingJob { Self::Table(_, table, ..) => table.name.clone(), Self::Index(index, _) => index.name.clone(), Self::Source(source) => source.name.clone(), - Self::Subscription(subscription) => subscription.name.clone(), } } @@ -256,7 +238,6 @@ impl StreamingJob { StreamingJob::Table(_, table, ..) => table.owner, StreamingJob::Index(index, _) => index.owner, StreamingJob::Source(source) => source.owner, - StreamingJob::Subscription(subscription) => subscription.owner, } } @@ -267,7 +248,6 @@ impl StreamingJob { Self::Index(_, table) => table.definition.clone(), Self::Sink(sink, _) => sink.definition.clone(), Self::Source(source) => source.definition.clone(), - Self::Subscription(subscription) => subscription.definition.clone(), } } @@ -306,7 +286,21 @@ impl StreamingJob { vec![] } StreamingJob::Source(_) => vec![], - Self::Subscription(subscription) => subscription.dependent_relations.clone(), + } + } + + pub fn dependent_secret_refs(&self) -> MetaResult> { + match self { + StreamingJob::Sink(sink, _) => Ok(get_refed_secret_ids_from_sink(sink)), + StreamingJob::Table(source, _, _) => { + if let Some(source) = source { + get_refed_secret_ids_from_source(source) + } else { + Ok(HashSet::new()) + } + } + StreamingJob::Source(source) => get_refed_secret_ids_from_source(source), + StreamingJob::MaterializedView(_) | StreamingJob::Index(_, _) => Ok(HashSet::new()), } } diff --git a/src/meta/src/manager/system_param/mod.rs b/src/meta/src/manager/system_param/mod.rs index bdd54d3ed7b55..0030d3c2d8f88 100644 --- a/src/meta/src/manager/system_param/mod.rs +++ b/src/meta/src/manager/system_param/mod.rs @@ -32,7 +32,7 @@ use tracing::info; use self::model::SystemParamsModel; use super::NotificationManagerRef; -use crate::model::{ValTransaction, VarTransaction}; +use crate::model::{InMemValTransaction, ValTransaction, VarTransaction}; use crate::storage::{MetaStore, MetaStoreRef, Transaction}; use crate::{MetaError, MetaResult}; diff --git a/src/meta/src/model/catalog.rs b/src/meta/src/model/catalog.rs index 8f762255d60b7..8d89080ae2462 100644 --- a/src/meta/src/model/catalog.rs +++ b/src/meta/src/model/catalog.rs @@ -13,7 +13,7 @@ // limitations under the License. use risingwave_pb::catalog::{ - Connection, Database, Function, Index, Schema, Sink, Source, Subscription, Table, View, + Connection, Database, Function, Index, Schema, Secret, Sink, Source, Subscription, Table, View, }; use crate::model::{MetadataModel, MetadataModelResult}; @@ -38,6 +38,8 @@ const CATALOG_SCHEMA_CF_NAME: &str = "cf/catalog_schema"; const CATALOG_DATABASE_CF_NAME: &str = "cf/catalog_database"; /// Column family name for database catalog. const CATALOG_SUBSCRIPTION_CF_NAME: &str = "cf/catalog_subscription"; +/// Column family name for secret catalog. +const CATALOG_SECRET_CF_NAME: &str = "cf/catalog_secret"; macro_rules! impl_model_for_catalog { ($name:ident, $cf:ident, $key_ty:ty, $key_fn:ident) => { @@ -74,6 +76,7 @@ impl_model_for_catalog!(Table, CATALOG_TABLE_CF_NAME, u32, get_id); impl_model_for_catalog!(Schema, CATALOG_SCHEMA_CF_NAME, u32, get_id); impl_model_for_catalog!(Database, CATALOG_DATABASE_CF_NAME, u32, get_id); impl_model_for_catalog!(Subscription, CATALOG_SUBSCRIPTION_CF_NAME, u32, get_id); +impl_model_for_catalog!(Secret, CATALOG_SECRET_CF_NAME, u32, get_id); #[cfg(test)] mod tests { @@ -93,7 +96,7 @@ mod tests { #[tokio::test] async fn test_database() -> MetadataModelResult<()> { let env = MetaSrvEnv::for_test().await; - let store = env.meta_store().as_kv(); + let store = env.meta_store_ref().as_kv(); let databases = Database::list(store).await?; assert!(databases.is_empty()); assert!(Database::select(store, &0).await.unwrap().is_none()); diff --git a/src/meta/src/model/mod.rs b/src/meta/src/model/mod.rs index 8b53d87160efa..1ca22854c5def 100644 --- a/src/meta/src/model/mod.rs +++ b/src/meta/src/model/mod.rs @@ -23,18 +23,17 @@ mod user; use std::collections::btree_map::{Entry, VacantEntry}; use std::collections::BTreeMap; use std::fmt::Debug; -use std::marker::PhantomData; +use std::io::{Read, Write}; use std::ops::{Deref, DerefMut}; +use anyhow::Context as _; use async_trait::async_trait; pub use cluster::*; pub use error::*; pub use migration_plan::*; pub use notification::*; -use prost::Message; pub use stream::*; -use crate::hummock::model::ext::Transaction as TransactionV2; use crate::storage::{MetaStore, MetaStoreError, Snapshot, Transaction}; /// A global, unique identifier of an actor @@ -59,13 +58,23 @@ mod private { pub trait MetadataModelMarker {} } +/// Compress the value if it's larger then the threshold to avoid hitting the limit of etcd. +/// +/// By default, the maximum size of any request to etcd is 1.5 MB. So we use a slightly +/// smaller value here. However, note that this is still a best-effort approach, as the +/// compressed size may still exceed the limit, in which case we should set the parameter +/// `--max-request-bytes` of etcd to a larger value. +const MODEL_COMPRESSION_THRESHOLD: usize = 1 << 20; + /// `MetadataModel` defines basic model operations in CRUD. +// TODO: better to move the methods that we don't want implementors to override to a separate +// extension trait. #[async_trait] pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker { /// Serialized prost message type. - type PbType: Message + Default; + type PbType: prost::Message + Default; /// Serialized key type. - type KeyType: Message; + type KeyType: prost::Message; /// Column family for this model. fn cf_name() -> String; @@ -73,17 +82,59 @@ pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker /// Serialize to protobuf. fn to_protobuf(&self) -> Self::PbType; - /// Serialize to protobuf encoded byte vector. - fn to_protobuf_encoded_vec(&self) -> Vec { - self.to_protobuf().encode_to_vec() - } - /// Deserialize from protobuf. fn from_protobuf(prost: Self::PbType) -> Self; /// Current record key. fn key(&self) -> MetadataModelResult; + /// Encode key to bytes. Should not be overridden. + fn encode_key(key: &Self::KeyType) -> Vec { + use prost::Message; + key.encode_to_vec() + } + + /// Encode value to bytes. Should not be overridden. + fn encode_value(value: &Self::PbType) -> Vec { + use flate2::write::GzEncoder; + use flate2::Compression; + use prost::Message; + + let pb_encoded = value.encode_to_vec(); + + // Compress the value if it's larger then the threshold to avoid hitting the limit of etcd. + if pb_encoded.len() > MODEL_COMPRESSION_THRESHOLD { + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&pb_encoded).unwrap(); + encoder.finish().unwrap() + } else { + pb_encoded + } + } + + /// Decode value from bytes. Should not be overridden. + fn decode_value(value: &[u8]) -> MetadataModelResult { + use flate2::bufread::GzDecoder; + use prost::Message; + + let mut decoder = GzDecoder::new(value); + let mut buf = Vec::new(); + + // If the value is compressed, decode it. + // This works because a protobuf-encoded message is never a valid gzip stream. + // https://stackoverflow.com/questions/63621784/can-a-protobuf-message-begin-with-a-gzip-magic-number + let value = if decoder.header().is_some() { + decoder + .read_to_end(&mut buf) + .context("failed to decode gzipped value")?; + buf.as_slice() + } else { + value + }; + + Self::PbType::decode(value).map_err(Into::into) + } + /// `list` returns all records in this model. async fn list(store: &S) -> MetadataModelResult> where @@ -92,11 +143,7 @@ pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker let bytes_vec = store.list_cf(&Self::cf_name()).await?; bytes_vec .iter() - .map(|(_k, v)| { - Self::PbType::decode(v.as_slice()) - .map(Self::from_protobuf) - .map_err(Into::into) - }) + .map(|(_k, v)| Self::decode_value(v.as_slice()).map(Self::from_protobuf)) .collect() } @@ -107,11 +154,7 @@ pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker let bytes_vec = snapshot.list_cf(&Self::cf_name()).await?; bytes_vec .iter() - .map(|(_k, v)| { - Self::PbType::decode(v.as_slice()) - .map(Self::from_protobuf) - .map_err(Into::into) - }) + .map(|(_k, v)| Self::decode_value(v.as_slice()).map(Self::from_protobuf)) .collect() } @@ -123,8 +166,8 @@ pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker store .put_cf( &Self::cf_name(), - self.key()?.encode_to_vec(), - self.to_protobuf().encode_to_vec(), + Self::encode_key(&self.key()?), + Self::encode_value(&self.to_protobuf()), ) .await .map_err(Into::into) @@ -136,7 +179,7 @@ pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker S: MetaStore, { store - .delete_cf(&Self::cf_name(), &key.encode_to_vec()) + .delete_cf(&Self::cf_name(), &Self::encode_key(key)) .await .map_err(Into::into) } @@ -146,7 +189,7 @@ pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker where S: MetaStore, { - let byte_vec = match store.get_cf(&Self::cf_name(), &key.encode_to_vec()).await { + let byte_vec = match store.get_cf(&Self::cf_name(), &Self::encode_key(key)).await { Ok(byte_vec) => byte_vec, Err(err) => { if !matches!(err, MetaStoreError::ItemNotFound(_)) { @@ -155,7 +198,7 @@ pub trait MetadataModel: std::fmt::Debug + Sized + private::MetadataModelMarker return Ok(None); } }; - let model = Self::from_protobuf(Self::PbType::decode(byte_vec.as_slice())?); + let model = Self::from_protobuf(Self::decode_value(byte_vec.as_slice())?); Ok(Some(model)) } } @@ -179,6 +222,7 @@ macro_rules! for_all_metadata_models { { risingwave_pb::user::UserInfo }, { risingwave_pb::catalog::Function }, { risingwave_pb::catalog::Connection }, + { risingwave_pb::catalog::Secret }, // These items need not be included in a meta snapshot. { crate::model::cluster::Worker }, { risingwave_pb::hummock::CompactTaskAssignment }, @@ -210,32 +254,28 @@ where async fn upsert_in_transaction(&self, trx: &mut Transaction) -> MetadataModelResult<()> { trx.put( Self::cf_name(), - self.key()?.encode_to_vec(), - self.to_protobuf_encoded_vec(), + Self::encode_key(&self.key()?), + Self::encode_value(&self.to_protobuf()), ); Ok(()) } async fn delete_in_transaction(&self, trx: &mut Transaction) -> MetadataModelResult<()> { - trx.delete(Self::cf_name(), self.key()?.encode_to_vec()); + trx.delete(Self::cf_name(), Self::encode_key(&self.key()?)); Ok(()) } } -/// Trait that wraps a local memory value and applies the change to the local memory value on -/// `commit` or leaves the local memory value untouched on `abort`. -pub trait ValTransaction: Sized { - type TXN; +pub trait InMemValTransaction: Sized { /// Commit the change to local memory value fn commit(self); +} +/// Trait that wraps a local memory value and applies the change to the local memory value on +/// `commit` or leaves the local memory value untouched on `abort`. +pub trait ValTransaction: InMemValTransaction { /// Apply the change (upsert or delete) to `txn` - async fn apply_to_txn(&self, txn: &mut Self::TXN) -> MetadataModelResult<()>; - - /// Abort the `VarTransaction` and leave the local memory value untouched - fn abort(self) { - drop(self); - } + async fn apply_to_txn(&self, txn: &mut TXN) -> MetadataModelResult<()>; } /// Transaction wrapper for a variable. @@ -244,28 +284,27 @@ pub trait ValTransaction: Sized { /// When `commit` is called, the change to `new_value` will be applied to the `orig_value_ref` /// When `abort` is called, the `VarTransaction` is dropped and the local memory value is /// untouched. -pub struct VarTransaction<'a, TXN, T: Transactional> { +pub struct VarTransaction<'a, T> { orig_value_ref: &'a mut T, new_value: Option, - _phantom: PhantomData, } -impl<'a, TXN, T> VarTransaction<'a, TXN, T> -where - T: Transactional, -{ +impl<'a, T> VarTransaction<'a, T> { /// Create a `VarTransaction` that wraps a raw variable - pub fn new(val_ref: &'a mut T) -> VarTransaction<'a, TXN, T> { + pub fn new(val_ref: &'a mut T) -> VarTransaction<'a, T> { VarTransaction { // lazy initialization new_value: None, orig_value_ref: val_ref, - _phantom: PhantomData, } } + + pub fn has_new_value(&self) -> bool { + self.new_value.is_some() + } } -impl<'a, TXN, T: Transactional> Deref for VarTransaction<'a, TXN, T> { +impl<'a, T> Deref for VarTransaction<'a, T> { type Target = T; fn deref(&self) -> &Self::Target { @@ -276,10 +315,7 @@ impl<'a, TXN, T: Transactional> Deref for VarTransaction<'a, TXN, T> { } } -impl<'a, TXN, T> DerefMut for VarTransaction<'a, TXN, T> -where - T: Clone + Transactional, -{ +impl<'a, T: Clone> DerefMut for VarTransaction<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { if self.new_value.is_none() { self.new_value.replace(self.orig_value_ref.clone()); @@ -288,19 +324,22 @@ where } } -impl<'a, TXN, T> ValTransaction for VarTransaction<'a, TXN, T> +impl<'a, T> InMemValTransaction for VarTransaction<'a, T> where - T: Transactional + PartialEq, + T: PartialEq, { - type TXN = TXN; - fn commit(self) { if let Some(new_value) = self.new_value { *self.orig_value_ref = new_value; } } +} - async fn apply_to_txn(&self, txn: &mut Self::TXN) -> MetadataModelResult<()> { +impl<'a, TXN, T> ValTransaction for VarTransaction<'a, T> +where + T: Transactional + PartialEq, +{ + async fn apply_to_txn(&self, txn: &mut TXN) -> MetadataModelResult<()> { if let Some(new_value) = &self.new_value { // Apply the change to `txn` only when the value is modified if *self.orig_value_ref != *new_value { @@ -423,27 +462,25 @@ enum BTreeMapOp { /// are stored in `staging`. On `commit`, it will apply the changes stored in `staging` to the in /// memory btree map. When serve `get` and `get_mut`, it merges the value stored in `staging` and /// `tree_ref`. -pub struct BTreeMapTransaction<'a, K: Ord, V, TXN = Transaction> { +pub struct BTreeMapTransaction<'a, K: Ord, V> { /// A reference to the original `BTreeMap`. All access to this field should be immutable, /// except when we commit the staging changes to the original map. tree_ref: &'a mut BTreeMap, /// Store all the staging changes that will be applied to the original map on commit staging: BTreeMap>, - _phantom: PhantomData, } -impl<'a, K: Ord + Debug, V: Clone, TXN> BTreeMapTransaction<'a, K, V, TXN> { - pub fn new(tree_ref: &'a mut BTreeMap) -> BTreeMapTransaction<'a, K, V, TXN> { +impl<'a, K: Ord + Debug, V: Clone> BTreeMapTransaction<'a, K, V> { + pub fn new(tree_ref: &'a mut BTreeMap) -> BTreeMapTransaction<'a, K, V> { Self { tree_ref, staging: BTreeMap::default(), - _phantom: PhantomData, } } /// Start a `BTreeMapEntryTransaction` when the `key` exists #[allow(dead_code)] - pub fn new_entry_txn(&mut self, key: K) -> Option> { + pub fn new_entry_txn(&mut self, key: K) -> Option> { BTreeMapEntryTransaction::new(self.tree_ref, key, None) } @@ -454,17 +491,13 @@ impl<'a, K: Ord + Debug, V: Clone, TXN> BTreeMapTransaction<'a, K, V, TXN> { &mut self, key: K, default_val: V, - ) -> BTreeMapEntryTransaction<'_, K, V, TXN> { + ) -> BTreeMapEntryTransaction<'_, K, V> { BTreeMapEntryTransaction::new(self.tree_ref, key, Some(default_val)) .expect("default value is provided and should return `Some`") } /// Start a `BTreeMapEntryTransaction` that inserts the `val` into `key`. - pub fn new_entry_insert_txn( - &mut self, - key: K, - val: V, - ) -> BTreeMapEntryTransaction<'_, K, V, TXN> { + pub fn new_entry_insert_txn(&mut self, key: K, val: V) -> BTreeMapEntryTransaction<'_, K, V> { BTreeMapEntryTransaction::new_insert(self.tree_ref, key, val) } @@ -472,10 +505,6 @@ impl<'a, K: Ord + Debug, V: Clone, TXN> BTreeMapTransaction<'a, K, V, TXN> { self.tree_ref } - pub fn tree_mut(&mut self) -> &mut BTreeMap { - self.tree_ref - } - /// Get the value of the provided key by merging the staging value and the original value pub fn get(&self, key: &K) -> Option<&V> { self.staging @@ -564,16 +593,16 @@ impl<'a, K: Ord + Debug, V: Clone, TXN> BTreeMapTransaction<'a, K, V, TXN> { } } -impl<'a, K: Ord + Debug, V: Transactional + Clone, TXN> ValTransaction - for BTreeMapTransaction<'a, K, V, TXN> -{ - type TXN = TXN; - +impl<'a, K: Ord + Debug, V: Clone> InMemValTransaction for BTreeMapTransaction<'a, K, V> { fn commit(self) { self.commit_memory(); } +} - async fn apply_to_txn(&self, txn: &mut Self::TXN) -> MetadataModelResult<()> { +impl<'a, K: Ord + Debug, V: Transactional + Clone, TXN> ValTransaction + for BTreeMapTransaction<'a, K, V> +{ + async fn apply_to_txn(&self, txn: &mut TXN) -> MetadataModelResult<()> { // Add the staging operation to txn for (k, op) in &self.staging { match op { @@ -590,26 +619,24 @@ impl<'a, K: Ord + Debug, V: Transactional + Clone, TXN> ValTransaction } /// Transaction wrapper for a `BTreeMap` entry value of given `key` -pub struct BTreeMapEntryTransaction<'a, K, V, TXN> { +pub struct BTreeMapEntryTransaction<'a, K, V> { tree_ref: &'a mut BTreeMap, pub key: K, pub new_value: V, - _phantom: PhantomData, } -impl<'a, K: Ord + Debug, V: Clone, TXN> BTreeMapEntryTransaction<'a, K, V, TXN> { +impl<'a, K: Ord + Debug, V: Clone> BTreeMapEntryTransaction<'a, K, V> { /// Create a `ValTransaction` that wraps a `BTreeMap` entry of the given `key`. /// If the tree does not contain `key`, the `default_val` will be used as the initial value pub fn new_insert( tree_ref: &'a mut BTreeMap, key: K, value: V, - ) -> BTreeMapEntryTransaction<'a, K, V, TXN> { + ) -> BTreeMapEntryTransaction<'a, K, V> { BTreeMapEntryTransaction { new_value: value, tree_ref, key, - _phantom: PhantomData, } } @@ -623,7 +650,7 @@ impl<'a, K: Ord + Debug, V: Clone, TXN> BTreeMapEntryTransaction<'a, K, V, TXN> tree_ref: &'a mut BTreeMap, key: K, default_val: Option, - ) -> Option> { + ) -> Option> { tree_ref .get(&key) .cloned() @@ -632,12 +659,11 @@ impl<'a, K: Ord + Debug, V: Clone, TXN> BTreeMapEntryTransaction<'a, K, V, TXN> new_value: orig_value, tree_ref, key, - _phantom: PhantomData, }) } } -impl<'a, K, V, TXN> Deref for BTreeMapEntryTransaction<'a, K, V, TXN> { +impl<'a, K, V> Deref for BTreeMapEntryTransaction<'a, K, V> { type Target = V; fn deref(&self) -> &Self::Target { @@ -645,22 +671,22 @@ impl<'a, K, V, TXN> Deref for BTreeMapEntryTransaction<'a, K, V, TXN> { } } -impl<'a, K, V, TXN> DerefMut for BTreeMapEntryTransaction<'a, K, V, TXN> { +impl<'a, K, V> DerefMut for BTreeMapEntryTransaction<'a, K, V> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.new_value } } -impl<'a, K: Ord, V: PartialEq + Transactional, TXN> ValTransaction - for BTreeMapEntryTransaction<'a, K, V, TXN> -{ - type TXN = TXN; - +impl<'a, K: Ord, V: PartialEq> InMemValTransaction for BTreeMapEntryTransaction<'a, K, V> { fn commit(self) { self.tree_ref.insert(self.key, self.new_value); } +} - async fn apply_to_txn(&self, txn: &mut Self::TXN) -> MetadataModelResult<()> { +impl<'a, K: Ord, V: PartialEq + Transactional, TXN> ValTransaction + for BTreeMapEntryTransaction<'a, K, V> +{ + async fn apply_to_txn(&self, txn: &mut TXN) -> MetadataModelResult<()> { if !self.tree_ref.contains_key(&self.key) || *self.tree_ref.get(&self.key).unwrap() != self.new_value { @@ -670,266 +696,10 @@ impl<'a, K: Ord, V: PartialEq + Transactional, TXN> ValTransaction } } -pub enum BTreeMapTransactionWrapper<'a, K: Ord, V> { - V1(BTreeMapTransaction<'a, K, V, Transaction>), - V2(BTreeMapTransaction<'a, K, V, TransactionV2>), -} - -impl<'a, K: Ord + Debug, V: Clone> BTreeMapTransactionWrapper<'a, K, V> { - pub fn tree_ref(&self) -> &BTreeMap { - match self { - BTreeMapTransactionWrapper::V1(v) => v.tree_ref, - BTreeMapTransactionWrapper::V2(v) => v.tree_ref, - } - } - - pub fn tree_mut(&mut self) -> &mut BTreeMap { - match self { - BTreeMapTransactionWrapper::V1(v) => v.tree_ref, - BTreeMapTransactionWrapper::V2(v) => v.tree_ref, - } - } - - /// Get the value of the provided key by merging the staging value and the original value - pub fn get(&self, key: &K) -> Option<&V> { - match self { - BTreeMapTransactionWrapper::V1(v) => v.get(key), - BTreeMapTransactionWrapper::V2(v) => v.get(key), - } - } - - pub fn contains_key(&self, key: &K) -> bool { - match self { - BTreeMapTransactionWrapper::V1(v) => v.contains_key(key), - BTreeMapTransactionWrapper::V2(v) => v.contains_key(key), - } - } - - pub fn get_mut(&mut self, key: K) -> Option> { - match self { - BTreeMapTransactionWrapper::V1(v) => v.get_mut(key), - BTreeMapTransactionWrapper::V2(v) => v.get_mut(key), - } - } - - pub fn insert(&mut self, key: K, value: V) { - match self { - BTreeMapTransactionWrapper::V1(v) => v.insert(key, value), - BTreeMapTransactionWrapper::V2(v) => v.insert(key, value), - } - } - - pub fn remove(&mut self, key: K) -> Option { - match self { - BTreeMapTransactionWrapper::V1(v) => v.remove(key), - BTreeMapTransactionWrapper::V2(v) => v.remove(key), - } - } - - pub fn commit_memory(self) { - match self { - BTreeMapTransactionWrapper::V1(v) => v.commit_memory(), - BTreeMapTransactionWrapper::V2(v) => v.commit_memory(), - } - } - - pub fn new_entry_txn_or_default( - &mut self, - key: K, - default_val: V, - ) -> BTreeMapEntryTransactionWrapper<'_, K, V> { - match self { - BTreeMapTransactionWrapper::V1(v) => BTreeMapEntryTransactionWrapper::V1( - BTreeMapEntryTransaction::new(v.tree_ref, key, Some(default_val)) - .expect("default value is provided and should return `Some`"), - ), - BTreeMapTransactionWrapper::V2(v) => BTreeMapEntryTransactionWrapper::V2( - BTreeMapEntryTransaction::new(v.tree_ref, key, Some(default_val)) - .expect("default value is provided and should return `Some`"), - ), - } - } - - pub fn new_entry_insert_txn( - &mut self, - key: K, - val: V, - ) -> BTreeMapEntryTransactionWrapper<'_, K, V> { - match self { - BTreeMapTransactionWrapper::V1(v) => BTreeMapEntryTransactionWrapper::V1( - BTreeMapEntryTransaction::new_insert(v.tree_ref, key, val), - ), - BTreeMapTransactionWrapper::V2(v) => BTreeMapEntryTransactionWrapper::V2( - BTreeMapEntryTransaction::new_insert(v.tree_ref, key, val), - ), - } - } -} - -impl<'a, K: Ord + Debug, V: Clone> BTreeMapTransactionWrapper<'a, K, V> { - pub fn into_v1(self) -> BTreeMapTransaction<'a, K, V, Transaction> { - match self { - BTreeMapTransactionWrapper::V1(v) => v, - BTreeMapTransactionWrapper::V2(_) => panic!("expect V1, found V2"), - } - } - - pub fn as_v1_ref(&self) -> &BTreeMapTransaction<'a, K, V, Transaction> { - match self { - BTreeMapTransactionWrapper::V1(v) => v, - BTreeMapTransactionWrapper::V2(_) => panic!("expect V1, found V2"), - } - } - - pub fn into_v2(self) -> BTreeMapTransaction<'a, K, V, TransactionV2> { - match self { - BTreeMapTransactionWrapper::V1(_) => panic!("expect V2, found V1"), - BTreeMapTransactionWrapper::V2(v) => v, - } - } - - pub fn as_v2_ref(&self) -> &BTreeMapTransaction<'a, K, V, TransactionV2> { - match self { - BTreeMapTransactionWrapper::V1(_) => panic!("expect V2, found V1"), - BTreeMapTransactionWrapper::V2(v) => v, - } - } -} - -pub enum BTreeMapEntryTransactionWrapper<'a, K, V> { - V1(BTreeMapEntryTransaction<'a, K, V, Transaction>), - V2(BTreeMapEntryTransaction<'a, K, V, TransactionV2>), -} - -impl<'a, K: Ord + Debug, V: Clone> Deref for BTreeMapEntryTransactionWrapper<'a, K, V> { - type Target = V; - - fn deref(&self) -> &Self::Target { - match self { - BTreeMapEntryTransactionWrapper::V1(v) => v.deref(), - BTreeMapEntryTransactionWrapper::V2(v) => v.deref(), - } - } -} - -impl<'a, K: Ord + Debug, V: Clone> DerefMut for BTreeMapEntryTransactionWrapper<'a, K, V> { - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - BTreeMapEntryTransactionWrapper::V1(v) => v.deref_mut(), - BTreeMapEntryTransactionWrapper::V2(v) => v.deref_mut(), - } - } -} - -impl<'a, K: Ord + Debug, V: Clone> BTreeMapEntryTransactionWrapper<'a, K, V> { - pub fn as_v1_ref(&self) -> &BTreeMapEntryTransaction<'a, K, V, Transaction> { - match self { - BTreeMapEntryTransactionWrapper::V1(v) => v, - BTreeMapEntryTransactionWrapper::V2(_) => { - panic!("expect V1, found V2") - } - } - } - - pub fn into_v1(self) -> BTreeMapEntryTransaction<'a, K, V, Transaction> { - match self { - BTreeMapEntryTransactionWrapper::V1(v) => v, - BTreeMapEntryTransactionWrapper::V2(_) => { - panic!("expect V1, found V2") - } - } - } - - pub fn as_v2_ref(&self) -> &BTreeMapEntryTransaction<'a, K, V, TransactionV2> { - match self { - BTreeMapEntryTransactionWrapper::V1(_) => { - panic!("expect V2, found V1") - } - BTreeMapEntryTransactionWrapper::V2(v) => v, - } - } - - pub fn into_v2(self) -> BTreeMapEntryTransaction<'a, K, V, TransactionV2> { - match self { - BTreeMapEntryTransactionWrapper::V1(_) => { - panic!("expect V2, found V1") - } - BTreeMapEntryTransactionWrapper::V2(v) => v, - } - } -} - -pub enum VarTransactionWrapper<'a, T: Transactional + Transactional> { - V1(VarTransaction<'a, Transaction, T>), - V2(VarTransaction<'a, TransactionV2, T>), -} - -impl<'a, T: Transactional + Transactional> - VarTransactionWrapper<'a, T> -{ - pub fn as_v1_ref(&self) -> &VarTransaction<'a, Transaction, T> { - match self { - VarTransactionWrapper::V1(v) => v, - VarTransactionWrapper::V2(_) => { - panic!("expect V1, found V2") - } - } - } - - pub fn into_v1(self) -> VarTransaction<'a, Transaction, T> { - match self { - VarTransactionWrapper::V1(v) => v, - VarTransactionWrapper::V2(_) => { - panic!("expect V1, found V2") - } - } - } - - pub fn as_v2_ref(&self) -> &VarTransaction<'a, TransactionV2, T> { - match self { - VarTransactionWrapper::V1(_) => { - panic!("expect V2, found V1") - } - VarTransactionWrapper::V2(v) => v, - } - } - - pub fn into_v2(self) -> VarTransaction<'a, TransactionV2, T> { - match self { - VarTransactionWrapper::V1(_) => { - panic!("expect V2, found V1") - } - VarTransactionWrapper::V2(v) => v, - } - } -} - -impl<'a, T: Transactional + Transactional> Deref - for VarTransactionWrapper<'a, T> -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - match self { - VarTransactionWrapper::V1(v) => v.deref(), - VarTransactionWrapper::V2(v) => v.deref(), - } - } -} - -impl<'a, T: Transactional + Transactional + Clone> DerefMut - for VarTransactionWrapper<'a, T> -{ - fn deref_mut(&mut self) -> &mut Self::Target { - match self { - VarTransactionWrapper::V1(v) => v.deref_mut(), - VarTransactionWrapper::V2(v) => v.deref_mut(), - } - } -} - #[cfg(test)] mod tests { + use itertools::Itertools; + use super::*; use crate::storage::Operation; @@ -958,6 +728,53 @@ mod tests { } } + #[tokio::test] + async fn test_compress_decompress() { + use prost::Message; + use risingwave_pb::catalog::Database; + + use crate::storage::MemStore; + + async fn do_test(len: usize) { + // Use `Database` as a test model. + type Model = Database; + + let store = MemStore::new(); + let model = Model { + name: "t".repeat(len), + ..Default::default() + }; + { + let encoded_len = model.encoded_len(); + // Showing that the encoded length is larger than the original length. + // So that a len greater than the threshold will hit the compression branch. + assert!(encoded_len >= len, "encoded_len: {encoded_len}, len: {len}"); + } + model.insert(&store).await.unwrap(); + + // Test `list` + let decoded = Model::list(&store) + .await + .unwrap() + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(model, decoded); + + // Test `select` + let decoded = Model::select(&store, &model.key().unwrap()) + .await + .unwrap() + .into_iter() + .exactly_one() + .unwrap(); + assert_eq!(model, decoded); + } + + do_test(1).await; + do_test(MODEL_COMPRESSION_THRESHOLD + 1).await; + } + #[tokio::test] async fn test_simple_var_transaction_commit() { let mut kv = TestTransactional { @@ -994,7 +811,6 @@ mod tests { }; let mut num_txn = VarTransaction::new(&mut kv); num_txn.value = "modified"; - num_txn.abort(); assert_eq!("original", kv.value); } diff --git a/src/meta/src/model/stream.rs b/src/meta/src/model/stream.rs index 058473580a96d..144a33ae7b24b 100644 --- a/src/meta/src/model/stream.rs +++ b/src/meta/src/model/stream.rs @@ -239,8 +239,8 @@ impl TableFragments { self.fragments.keys().cloned() } - pub fn fragments(&self) -> Vec<&Fragment> { - self.fragments.values().collect_vec() + pub fn fragments(&self) -> impl Iterator { + self.fragments.values() } /// Returns the table id. @@ -345,11 +345,6 @@ impl TableFragments { .collect() } - /// Returns barrier inject actor ids. - pub fn barrier_inject_actor_ids(&self) -> Vec { - Self::filter_actor_ids(self, Self::is_injectable) - } - /// Check if the fragment type mask is injectable. pub fn is_injectable(fragment_type_mask: u32) -> bool { (fragment_type_mask @@ -561,20 +556,6 @@ impl TableFragments { actors } - pub fn worker_barrier_inject_actor_states( - &self, - ) -> BTreeMap> { - let mut map = BTreeMap::default(); - let barrier_inject_actor_ids = self.barrier_inject_actor_ids(); - for &actor_id in &barrier_inject_actor_ids { - let actor_status = &self.actor_status[&actor_id]; - map.entry(actor_status.get_parallel_unit().unwrap().worker_node_id as WorkerId) - .or_insert_with(Vec::new) - .push((actor_id, actor_status.state())); - } - map - } - /// Returns actor map: `actor_id` => `StreamActor`. pub fn actor_map(&self) -> HashMap { let mut actor_map = HashMap::default(); diff --git a/src/meta/src/rpc/ddl_controller.rs b/src/meta/src/rpc/ddl_controller.rs index c0aa62750facb..6bcc59fee5538 100644 --- a/src/meta/src/rpc/ddl_controller.rs +++ b/src/meta/src/rpc/ddl_controller.rs @@ -18,16 +18,19 @@ use std::num::NonZeroUsize; use std::sync::Arc; use std::time::Duration; +use aes_siv::aead::generic_array::GenericArray; +use aes_siv::aead::Aead; +use aes_siv::{Aes128SivAead, KeyInit}; use anyhow::Context; use itertools::Itertools; -use rand::Rng; +use rand::{Rng, RngCore}; use risingwave_common::config::DefaultParallelism; use risingwave_common::hash::{ParallelUnitMapping, VirtualNode}; use risingwave_common::system_param::reader::SystemParamsRead; use risingwave_common::util::column_index_mapping::ColIndexMapping; use risingwave_common::util::epoch::Epoch; use risingwave_common::util::stream_graph_visitor::{ - visit_fragment, visit_stream_node, visit_stream_node_cont, + visit_fragment, visit_stream_node, visit_stream_node_cont_mut, }; use risingwave_common::{bail, current_cluster_version}; use risingwave_connector::dispatch_source_prop; @@ -45,7 +48,7 @@ use risingwave_pb::catalog::source::OptionalAssociatedTableId; use risingwave_pb::catalog::table::OptionalAssociatedSourceId; use risingwave_pb::catalog::{ connection, Comment, Connection, CreateType, Database, Function, PbSource, PbTable, Schema, - Sink, Source, Table, View, + Secret, Sink, Source, Subscription, Table, View, }; use risingwave_pb::ddl_service::alter_owner_request::Object; use risingwave_pb::ddl_service::{ @@ -58,6 +61,7 @@ use risingwave_pb::stream_plan::{ Dispatcher, DispatcherType, FragmentTypeFlag, MergeNode, PbStreamFragmentGraph, StreamFragmentGraph as StreamFragmentGraphProto, }; +use serde::{Deserialize, Serialize}; use thiserror_ext::AsReport; use tokio::sync::Semaphore; use tokio::time::sleep; @@ -65,11 +69,13 @@ use tracing::log::warn; use tracing::Instrument; use crate::barrier::BarrierManagerRef; +use crate::error::MetaErrorInner; use crate::manager::{ - CatalogManagerRef, ConnectionId, DatabaseId, FragmentManagerRef, FunctionId, IdCategory, - IdCategoryType, IndexId, LocalNotification, MetaSrvEnv, MetadataManager, MetadataManagerV1, - NotificationVersion, RelationIdEnum, SchemaId, SinkId, SourceId, StreamingClusterInfo, - StreamingJob, SubscriptionId, TableId, UserId, ViewId, IGNORED_NOTIFICATION_VERSION, + CatalogManagerRef, ConnectionId, DatabaseId, DdlType, FragmentManagerRef, FunctionId, + IdCategory, IdCategoryType, IndexId, LocalNotification, MetaSrvEnv, MetadataManager, + MetadataManagerV1, NotificationVersion, RelationIdEnum, SchemaId, SecretId, SinkId, SourceId, + StreamingClusterInfo, StreamingJob, StreamingJobDiscriminants, SubscriptionId, TableId, UserId, + ViewId, IGNORED_NOTIFICATION_VERSION, }; use crate::model::{FragmentId, StreamContext, TableFragments, TableParallelism}; use crate::rpc::cloud_provider::AwsEc2Client; @@ -101,7 +107,6 @@ pub enum StreamingJobId { Sink(SinkId), Table(Option, TableId), Index(IndexId), - Subscription(SubscriptionId), } impl StreamingJobId { @@ -110,7 +115,6 @@ impl StreamingJobId { match self { StreamingJobId::MaterializedView(id) | StreamingJobId::Sink(id) - | StreamingJobId::Subscription(id) | StreamingJobId::Table(_, id) | StreamingJobId::Index(id) => *id, } @@ -149,7 +153,17 @@ pub enum DdlCommand { AlterSetSchema(alter_set_schema_request::Object, SchemaId), CreateConnection(Connection), DropConnection(ConnectionId), + CreateSecret(Secret), + DropSecret(SecretId), CommentOn(Comment), + CreateSubscription(Subscription), + DropSubscription(SubscriptionId, DropMode), +} + +#[derive(Deserialize, Serialize)] +struct SecretEncryption { + nonce: [u8; 16], + ciphertext: Vec, } impl DdlCommand { @@ -161,7 +175,9 @@ impl DdlCommand { | DdlCommand::DropFunction(_) | DdlCommand::DropView(_, _) | DdlCommand::DropStreamingJob(_, _, _) - | DdlCommand::DropConnection(_) => true, + | DdlCommand::DropConnection(_) + | DdlCommand::DropSecret(_) => true, + // Simply ban all other commands in recovery. _ => false, } @@ -330,8 +346,16 @@ impl DdlController { DdlCommand::DropConnection(connection_id) => { ctrl.drop_connection(connection_id).await } + DdlCommand::CreateSecret(secret) => ctrl.create_secret(secret).await, + DdlCommand::DropSecret(secret_id) => ctrl.drop_secret(secret_id).await, DdlCommand::AlterSourceColumn(source) => ctrl.alter_source_column(source).await, DdlCommand::CommentOn(comment) => ctrl.comment_on(comment).await, + DdlCommand::CreateSubscription(subscription) => { + ctrl.create_subscription(subscription).await + } + DdlCommand::DropSubscription(subscription_id, drop_mode) => { + ctrl.drop_subscription(subscription_id, drop_mode).await + } } } .in_current_span(); @@ -602,6 +626,57 @@ impl DdlController { } } + async fn create_secret(&self, mut secret: Secret) -> MetaResult { + // The 'secret' part of the request we receive from the frontend is in plaintext; + // here, we need to encrypt it before storing it in the catalog. + + let encrypted_payload = { + let data = secret.get_value().as_slice(); + let key = self.env.opts.secret_store_private_key.as_slice(); + let encrypt_key = { + let mut k = key[..(std::cmp::min(key.len(), 32))].to_vec(); + k.resize_with(32, || 0); + k + }; + + let mut rng = rand::thread_rng(); + let mut nonce: [u8; 16] = [0; 16]; + rng.fill_bytes(&mut nonce); + let nonce_array = GenericArray::from_slice(&nonce); + let cipher = Aes128SivAead::new(encrypt_key.as_slice().into()); + + let ciphertext = cipher.encrypt(nonce_array, data).map_err(|e| { + MetaError::from(MetaErrorInner::InvalidParameter(format!( + "failed to encrypt secret {}: {:?}", + secret.name, e + ))) + })?; + bincode::serialize(&SecretEncryption { nonce, ciphertext }).map_err(|e| { + MetaError::from(MetaErrorInner::InvalidParameter(format!( + "failed to serialize secret {}: {:?}", + secret.name, + e.as_report() + ))) + })? + }; + secret.value = encrypted_payload; + + match &self.metadata_manager { + MetadataManager::V1(mgr) => { + secret.id = self.gen_unique_id::<{ IdCategory::Secret }>().await?; + mgr.catalog_manager.create_secret(secret).await + } + MetadataManager::V2(mgr) => mgr.catalog_controller.create_secret(secret).await, + } + } + + async fn drop_secret(&self, secret_id: SecretId) -> MetaResult { + match &self.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.drop_secret(secret_id).await, + MetadataManager::V2(mgr) => mgr.catalog_controller.drop_secret(secret_id as _).await, + } + } + pub(crate) async fn delete_vpc_endpoint(&self, connection: &Connection) -> MetaResult<()> { // delete AWS vpc endpoint if let Some(connection::Info::PrivateLinkService(svc)) = &connection.info @@ -634,6 +709,114 @@ impl DdlController { Ok(()) } + async fn create_subscription( + &self, + mut subscription: Subscription, + ) -> MetaResult { + tracing::debug!("create subscription"); + let _permit = self + .creating_streaming_job_permits + .semaphore + .acquire() + .await + .unwrap(); + let _reschedule_job_lock = self.stream_manager.reschedule_lock_read_guard().await; + match &self.metadata_manager { + MetadataManager::V1(mgr) => { + let id = self.gen_unique_id::<{ IdCategory::Table }>().await?; + let initialized_at_epoch = Some(Epoch::now().0); + let initialized_at_cluster_version = Some(current_cluster_version()); + subscription.initialized_at_epoch = initialized_at_epoch; + subscription.initialized_at_cluster_version = initialized_at_cluster_version; + subscription.id = id; + + mgr.catalog_manager + .start_create_subscription_procedure(&subscription) + .await?; + match self.stream_manager.create_subscription(&subscription).await { + Ok(_) => { + let version = mgr + .catalog_manager + .notify_create_subscription(subscription.id) + .await?; + tracing::debug!("finish create subscription"); + Ok(version) + } + Err(e) => { + tracing::debug!("cancel create subscription"); + Err(e) + } + } + } + MetadataManager::V2(mgr) => { + mgr.catalog_controller + .create_subscription_catalog(&mut subscription) + .await?; + match self.stream_manager.create_subscription(&subscription).await { + Ok(_) => { + let version = mgr + .catalog_controller + .notify_create_subscription(subscription.id) + .await?; + tracing::debug!("finish create subscription"); + Ok(version) + } + Err(e) => { + tracing::debug!("cancel create subscription"); + Err(e) + } + } + } + } + } + + async fn drop_subscription( + &self, + subscription_id: SubscriptionId, + drop_mode: DropMode, + ) -> MetaResult { + tracing::debug!("preparing drop subscription"); + let _reschedule_job_lock = self.stream_manager.reschedule_lock_read_guard().await; + match &self.metadata_manager { + MetadataManager::V1(mgr) => { + let table_id = mgr + .catalog_manager + .get_subscription_by_id(subscription_id) + .await? + .dependent_table_id; + let (version, _) = mgr + .catalog_manager + .drop_relation( + RelationIdEnum::Subscription(subscription_id), + mgr.fragment_manager.clone(), + drop_mode, + ) + .await?; + self.stream_manager + .drop_subscription(subscription_id, table_id) + .await; + tracing::debug!("finish drop subscription"); + Ok(version) + } + MetadataManager::V2(mgr) => { + let table_id = mgr + .catalog_controller + .get_subscription_by_id(subscription_id as i32) + .await? + .dependent_table_id; + let (_, version) = mgr + .catalog_controller + .drop_relation(ObjectType::Subscription, subscription_id as _, drop_mode) + .await?; + self.stream_manager + .drop_subscription(subscription_id, table_id) + .await; + tracing::debug!("finish drop subscription"); + Ok(version) + } + } + } + async fn create_streaming_job( &self, mut stream_job: StreamingJob, @@ -712,13 +895,26 @@ impl DdlController { fragment_graph, .. } = replace_table_info; - let fragment_graph = self + let fragment_graph = match self .prepare_replace_table( mgr.catalog_manager.clone(), &mut streaming_job, fragment_graph, ) - .await?; + .await + { + Ok(fragment_graph) => fragment_graph, + Err(err) => { + tracing::error!(error = %err.as_report(), id = stream_job.id(), "failed to prepare streaming job"); + let StreamingJob::Sink(sink, _) = &stream_job else { + unreachable!("unexpected job: {stream_job:?}"); + }; + mgr.catalog_manager + .cancel_create_sink_procedure(sink, &None) + .await; + return Err(err); + } + }; Some((streaming_job, fragment_graph)) } @@ -793,9 +989,9 @@ impl DdlController { ctx, internal_tables, ) - .await + .await } - (CreateType::Background, _) => { + (CreateType::Background, &StreamingJob::MaterializedView(_)) => { let ctrl = self.clone(); let mgr = mgr.clone(); let stream_job_id = stream_job.id(); @@ -821,6 +1017,10 @@ impl DdlController { tokio::spawn(fut); Ok(IGNORED_NOTIFICATION_VERSION) } + (CreateType::Background, _) => { + let d: StreamingJobDiscriminants = stream_job.into(); + bail!("background_ddl not supported for: {:?}", d) + } } } @@ -853,11 +1053,7 @@ impl DdlController { actor.nodes.as_ref().unwrap().node_body && let Some(ref cdc_table_desc) = stream_cdc_scan.cdc_table_desc { - let properties: HashMap = cdc_table_desc - .connect_properties - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); + let properties = cdc_table_desc.connect_properties.clone(); let mut props = ConnectorProperties::extract(properties, true)?; props.init_from_pb_cdc_table_desc(cdc_table_desc); @@ -1043,7 +1239,7 @@ impl DdlController { if let Some(node) = &mut actor.nodes { let fields = node.fields.clone(); - visit_stream_node_cont(node, |node| { + visit_stream_node_cont_mut(node, |node| { if let Some(NodeBody::Union(_)) = &mut node.node_body { for input in &mut node.input { if let Some(NodeBody::Merge(merge_node)) = &mut input.node_body @@ -1134,8 +1330,9 @@ impl DdlController { }; tracing::debug!(id = job_id, "finishing stream job"); - let version = self - .finish_stream_job(mgr, stream_job, internal_tables) + let version = mgr + .catalog_manager + .finish_stream_job(stream_job, internal_tables) .await?; tracing::debug!(id = job_id, "finished stream job"); @@ -1159,7 +1356,6 @@ impl DdlController { StreamingJobId::Sink(id) => (id as _, ObjectType::Sink), StreamingJobId::Table(_, id) => (id as _, ObjectType::Table), StreamingJobId::Index(idx) => (idx as _, ObjectType::Index), - StreamingJobId::Subscription(id) => (id as _, ObjectType::Subscription), }; let version = self @@ -1217,15 +1413,6 @@ impl DdlController { ) .await? } - StreamingJobId::Subscription(subscription_id) => { - mgr.catalog_manager - .drop_relation( - RelationIdEnum::Subscription(subscription_id), - mgr.fragment_manager.clone(), - drop_mode, - ) - .await? - } }; if let Some(replace_table_info) = target_replace_info { @@ -1316,14 +1503,13 @@ impl DdlController { ) -> MetaResult { const MAX_PARALLELISM: NonZeroUsize = NonZeroUsize::new(VirtualNode::COUNT).unwrap(); - if cluster_info.parallel_units.is_empty() { + if cluster_info.parallelism() == 0 { return Err(MetaError::unavailable( "No available parallel units to schedule", )); } - let available_parallel_units = - NonZeroUsize::new(cluster_info.parallel_units.len()).unwrap(); + let available_parallel_units = NonZeroUsize::new(cluster_info.parallelism()).unwrap(); // Use configured parallel units if no default parallelism is specified. let parallelism = @@ -1528,7 +1714,10 @@ impl DdlController { // barrier manager will do the cleanup. let result = mgr .catalog_manager - .cancel_create_table_procedure(table.id, creating_internal_table_ids.clone()) + .cancel_create_materialized_view_procedure( + table.id, + creating_internal_table_ids.clone(), + ) .await; creating_internal_table_ids.push(table.id); if let Err(e) = result { @@ -1543,30 +1732,15 @@ impl DdlController { .cancel_create_sink_procedure(sink, target_table) .await; } - StreamingJob::Subscription(subscription) => { - mgr.catalog_manager - .cancel_create_subscription_procedure(subscription) - .await; - } StreamingJob::Table(source, table, ..) => { if let Some(source) = source { mgr.catalog_manager .cancel_create_table_procedure_with_source(source, table) - .await; + .await?; } else { - let result = mgr - .catalog_manager - .cancel_create_table_procedure( - table.id, - creating_internal_table_ids.clone(), - ) + mgr.catalog_manager + .cancel_create_table_procedure(table) .await; - if let Err(e) = result { - tracing::warn!( - error = %e.as_report(), - "Failed to cancel create table procedure, perhaps barrier manager has already cleaned it." - ); - } } creating_internal_table_ids.push(table.id); } @@ -1589,89 +1763,6 @@ impl DdlController { Ok(()) } - /// `finish_stream_job` finishes a stream job and clean some states. - async fn finish_stream_job( - &self, - mgr: &MetadataManagerV1, - mut stream_job: StreamingJob, - internal_tables: Vec
, - ) -> MetaResult { - // 1. finish procedure. - let mut creating_internal_table_ids = internal_tables.iter().map(|t| t.id).collect_vec(); - - // Update the corresponding 'created_at' field. - stream_job.mark_created(); - - let version = match stream_job { - StreamingJob::MaterializedView(table) => { - creating_internal_table_ids.push(table.id); - mgr.catalog_manager - .finish_create_table_procedure(internal_tables, table) - .await? - } - StreamingJob::Sink(sink, target_table) => { - let sink_id = sink.id; - - let mut version = mgr - .catalog_manager - .finish_create_sink_procedure(internal_tables, sink) - .await?; - - if let Some((table, source)) = target_table { - let streaming_job = - StreamingJob::Table(source, table, TableJobType::Unspecified); - - version = self - .finish_replace_table( - mgr.catalog_manager.clone(), - &streaming_job, - None, - Some(sink_id), - None, - ) - .await?; - } - - version - } - StreamingJob::Subscription(subscription) => { - mgr.catalog_manager - .finish_create_subscription_procedure(internal_tables, subscription) - .await? - } - StreamingJob::Table(source, table, ..) => { - creating_internal_table_ids.push(table.id); - if let Some(source) = source { - mgr.catalog_manager - .finish_create_table_procedure_with_source(source, table, internal_tables) - .await? - } else { - mgr.catalog_manager - .finish_create_table_procedure(internal_tables, table) - .await? - } - } - StreamingJob::Index(index, table) => { - creating_internal_table_ids.push(table.id); - mgr.catalog_manager - .finish_create_index_procedure(internal_tables, index, table) - .await? - } - StreamingJob::Source(source) => { - mgr.catalog_manager - .finish_create_source_procedure(source, internal_tables) - .await? - } - }; - - // 2. unmark creating tables. - mgr.catalog_manager - .unmark_creating_tables(&creating_internal_table_ids, false) - .await; - - Ok(version) - } - async fn drop_table_inner( &self, source_id: Option, @@ -1830,29 +1921,58 @@ impl DdlController { .mview_fragment() .expect("mview fragment not found"); + let ddl_type = DdlType::from(stream_job); + let DdlType::Table(table_job_type) = &ddl_type else { + bail!( + "only support replacing table streaming job, ddl_type: {:?}", + ddl_type + ) + }; + // Map the column indices in the dispatchers with the given mapping. let downstream_fragments = self.metadata_manager.get_downstream_chain_fragments(id).await? - .into_iter() - .map(|(d, f)| - if let Some(mapping) = &table_col_index_mapping { - Some((mapping.rewrite_dispatch_strategy(&d)?, f)) - } else { - Some((d, f)) - }) - .collect::>() - .ok_or_else(|| { - // The `rewrite` only fails if some column is dropped. - MetaError::invalid_parameter( - "unable to drop the column due to being referenced by downstream materialized views or sinks", - ) - })?; + .into_iter() + .map(|(d, f)| + if let Some(mapping) = &table_col_index_mapping { + Some((mapping.rewrite_dispatch_strategy(&d)?, f)) + } else { + Some((d, f)) + }) + .collect::>() + .ok_or_else(|| { + // The `rewrite` only fails if some column is dropped. + MetaError::invalid_parameter( + "unable to drop the column due to being referenced by downstream materialized views or sinks", + ) + })?; - let complete_graph = CompleteStreamFragmentGraph::with_downstreams( - fragment_graph, - original_table_fragment.fragment_id, - downstream_fragments, - stream_job.into(), - )?; + // build complete graph based on the table job type + let complete_graph = match table_job_type { + TableJobType::General => CompleteStreamFragmentGraph::with_downstreams( + fragment_graph, + original_table_fragment.fragment_id, + downstream_fragments, + ddl_type, + )?, + + TableJobType::SharedCdcSource => { + // get the upstream fragment which should be the cdc source + let upstream_root_fragments = self + .metadata_manager + .get_upstream_root_fragments(fragment_graph.dependent_table_ids()) + .await?; + CompleteStreamFragmentGraph::with_upstreams_and_downstreams( + fragment_graph, + upstream_root_fragments, + original_table_fragment.fragment_id, + downstream_fragments, + ddl_type, + )? + } + TableJobType::Unspecified => { + unreachable!() + } + }; // 2. Build the actor graph. let cluster_info = self.metadata_manager.get_streaming_cluster_info().await?; @@ -1872,7 +1992,11 @@ impl DdlController { } = actor_graph_builder .generate_graph(&self.env, stream_job, expr_context) .await?; - assert!(dispatchers.is_empty()); + + // general table job type does not have upstream job, so the dispatchers should be empty + if matches!(table_job_type, TableJobType::General) { + assert!(dispatchers.is_empty()); + } // 3. Build the table fragments structure that will be persisted in the stream manager, and // the context that contains all information needed for building the actors on the compute diff --git a/src/meta/src/rpc/ddl_controller_v2.rs b/src/meta/src/rpc/ddl_controller_v2.rs index 3e948e88e2821..0dabc9b19022d 100644 --- a/src/meta/src/rpc/ddl_controller_v2.rs +++ b/src/meta/src/rpc/ddl_controller_v2.rs @@ -104,7 +104,7 @@ impl DdlController { self.env.event_log_manager_ref().add_event_logs(vec![ risingwave_pb::meta::event_log::Event::CreateStreamJobFail(event), ]); - let (aborted, _) = mgr + let aborted = mgr .catalog_controller .try_abort_creating_streaming_job(job_id as _, false) .await?; @@ -214,7 +214,7 @@ impl DdlController { ( streaming_job.clone(), ctx.merge_updates.clone(), - table_fragments.table_id(), + table_fragments.table_id().table_id(), ) }, ); @@ -223,25 +223,11 @@ impl DdlController { .create_streaming_job(table_fragments, ctx) .await?; - let mut version = mgr + let version = mgr .catalog_controller - .finish_streaming_job(stream_job_id as _) + .finish_streaming_job(stream_job_id as _, replace_table_job_info) .await?; - if let Some((streaming_job, merge_updates, table_id)) = replace_table_job_info { - version = mgr - .catalog_controller - .finish_replace_streaming_job( - table_id.table_id as _, - streaming_job, - merge_updates, - None, - Some(stream_job_id), - None, - ) - .await?; - } - Ok(version) } (CreateType::Background, _) => { @@ -257,7 +243,7 @@ impl DdlController { if result.is_ok() { let _ = mgr .catalog_controller - .finish_streaming_job(stream_job_id as _) + .finish_streaming_job(stream_job_id as _, None) .await.inspect_err(|err| { tracing::error!(id = stream_job_id, error = ?err.as_report(), "failed to finish background streaming job"); }); @@ -398,6 +384,7 @@ impl DdlController { connections, source_fragments, removed_actors, + removed_fragments, } = release_ctx; // delete vpc endpoints. @@ -437,6 +424,7 @@ impl DdlController { removed_actors.into_iter().map(|id| id as _).collect(), streaming_job_ids, state_table_ids, + removed_fragments.iter().map(|id| *id as _).collect(), ) .await; diff --git a/src/meta/src/rpc/election/sql.rs b/src/meta/src/rpc/election/sql.rs index 65c3ad613dde0..9ec5bd199cf76 100644 --- a/src/meta/src/rpc/election/sql.rs +++ b/src/meta/src/rpc/election/sql.rs @@ -64,6 +64,8 @@ pub trait SqlDriver: Send + Sync + 'static { async fn candidates(&self, service_name: &str) -> MetaResult>; async fn resign(&self, service_name: &str, id: &str) -> MetaResult<()>; + + async fn trim_candidates(&self, service_name: &str, timeout: i64) -> MetaResult<()>; } pub trait SqlDriverCommon { @@ -263,6 +265,23 @@ DO Ok(()) } + + async fn trim_candidates(&self, service_name: &str, timeout: i64) -> MetaResult<()> { + self.conn + .execute(Statement::from_sql_and_values( + DatabaseBackend::Sqlite, + format!( + r#" + DELETE FROM {table} WHERE service = $1 AND DATETIME({table}.last_heartbeat, '+' || $2 || ' second') < CURRENT_TIMESTAMP; + "#, + table = Self::member_table_name() + ), + vec![Value::from(service_name), Value::from(timeout)], + )) + .await?; + + Ok(()) + } } #[async_trait::async_trait] @@ -421,6 +440,23 @@ impl SqlDriver for MySqlDriver { Ok(()) } + + async fn trim_candidates(&self, service_name: &str, timeout: i64) -> MetaResult<()> { + self.conn + .execute(Statement::from_sql_and_values( + DatabaseBackend::MySql, + format!( + r#" + DELETE FROM {table} WHERE service = ? AND last_heartbeat < NOW() - INTERVAL ? SECOND; + "#, + table = Self::member_table_name() + ), + vec![Value::from(service_name), Value::from(timeout)], + )) + .await?; + + Ok(()) + } } #[async_trait::async_trait] @@ -579,6 +615,23 @@ impl SqlDriver for PostgresDriver { Ok(()) } + + async fn trim_candidates(&self, service_name: &str, timeout: i64) -> MetaResult<()> { + self.conn + .execute(Statement::from_sql_and_values( + DatabaseBackend::Postgres, + format!( + r#" + DELETE FROM {table} WHERE {table}.service = $1 AND {table}.last_heartbeat < NOW() - $2::INTERVAL; + "#, + table = Self::member_table_name() + ), + vec![Value::from(service_name), Value::from(timeout.to_string())], + )) + .await?; + + Ok(()) + } } #[async_trait::async_trait] @@ -638,13 +691,15 @@ where let mut election_ticker = time::interval(Duration::from_secs(1)); + let mut prev_leader = "".to_string(); + loop { tokio::select! { - _ = election_ticker.tick() => { - let election_row = self - .driver - .try_campaign(META_ELECTION_KEY, self.id.as_str(), ttl) - .await?; + _ = election_ticker.tick() => { + let election_row = self + .driver + .try_campaign(META_ELECTION_KEY, self.id.as_str(), ttl) + .await?; assert_eq!(election_row.service, META_ELECTION_KEY); @@ -652,14 +707,25 @@ where if !is_leader{ self.is_leader_sender.send_replace(true); is_leader = true; + } else { + self.is_leader_sender.send_replace(false); } } else if is_leader { tracing::warn!("leader has been changed to {}", election_row.id); break; + } else if prev_leader != election_row.id { + tracing::info!("leader is {}", election_row.id); + prev_leader.clone_from(&election_row.id) } - timeout_ticker.reset(); - } + timeout_ticker.reset(); + + if is_leader { + if let Err(e) = self.driver.trim_candidates(META_ELECTION_KEY, ttl * 2).await { + tracing::warn!(error = %e.as_report(), "trim candidates failed"); + } + } + } _ = timeout_ticker.tick() => { tracing::error!("member {} election timeout", self.id); break; diff --git a/src/meta/src/rpc/metrics.rs b/src/meta/src/rpc/metrics.rs index 2df847516c4a8..28520720e98fe 100644 --- a/src/meta/src/rpc/metrics.rs +++ b/src/meta/src/rpc/metrics.rs @@ -449,7 +449,7 @@ impl MetaMetrics { let hummock_manager_lock_time = register_histogram_vec_with_registry!( "hummock_manager_lock_time", "latency for hummock manager to acquire the rwlock", - &["method", "lock_name", "lock_type"], + &["lock_name", "lock_type"], registry ) .unwrap(); diff --git a/src/meta/src/serving/mod.rs b/src/meta/src/serving/mod.rs index 36e7b77ccf63a..69e17a978212e 100644 --- a/src/meta/src/serving/mod.rs +++ b/src/meta/src/serving/mod.rs @@ -16,11 +16,11 @@ use std::collections::HashMap; use std::sync::Arc; use parking_lot::RwLock; -use risingwave_common::hash::ParallelUnitMapping; +use risingwave_common::hash::WorkerSlotMapping; use risingwave_common::vnode_mapping::vnode_placement::place_vnode; use risingwave_pb::common::{WorkerNode, WorkerType}; use risingwave_pb::meta::subscribe_response::{Info, Operation}; -use risingwave_pb::meta::{FragmentParallelUnitMapping, FragmentParallelUnitMappings}; +use risingwave_pb::meta::{FragmentWorkerSlotMapping, FragmentWorkerSlotMappings}; use tokio::sync::oneshot::Sender; use tokio::task::JoinHandle; @@ -31,11 +31,11 @@ pub type ServingVnodeMappingRef = Arc; #[derive(Default)] pub struct ServingVnodeMapping { - serving_vnode_mappings: RwLock>, + serving_vnode_mappings: RwLock>, } impl ServingVnodeMapping { - pub fn all(&self) -> HashMap { + pub fn all(&self) -> HashMap { self.serving_vnode_mappings.read().clone() } @@ -45,9 +45,9 @@ impl ServingVnodeMapping { &self, streaming_parallelisms: HashMap, workers: &[WorkerNode], - ) -> (HashMap, Vec) { + ) -> (HashMap, Vec) { let mut serving_vnode_mappings = self.serving_vnode_mappings.write(); - let mut upserted: HashMap = HashMap::default(); + let mut upserted: HashMap = HashMap::default(); let mut failed: Vec = vec![]; for (fragment_id, streaming_parallelism) in streaming_parallelisms { let new_mapping = { @@ -81,24 +81,24 @@ impl ServingVnodeMapping { } } -pub(crate) fn to_fragment_parallel_unit_mapping( - mappings: &HashMap, -) -> Vec { +pub(crate) fn to_fragment_worker_slot_mapping( + mappings: &HashMap, +) -> Vec { mappings .iter() - .map(|(fragment_id, mapping)| FragmentParallelUnitMapping { + .map(|(fragment_id, mapping)| FragmentWorkerSlotMapping { fragment_id: *fragment_id, mapping: Some(mapping.to_protobuf()), }) .collect() } -pub(crate) fn to_deleted_fragment_parallel_unit_mapping( +pub(crate) fn to_deleted_fragment_worker_slot_mapping( fragment_ids: &[FragmentId], -) -> Vec { +) -> Vec { fragment_ids .iter() - .map(|fragment_id| FragmentParallelUnitMapping { + .map(|fragment_id| FragmentWorkerSlotMapping { fragment_id: *fragment_id, mapping: None, }) @@ -120,8 +120,8 @@ pub async fn on_meta_start( ); notification_manager.notify_frontend_without_version( Operation::Snapshot, - Info::ServingParallelUnitMappings(FragmentParallelUnitMappings { - mappings: to_fragment_parallel_unit_mapping(&mappings), + Info::ServingWorkerSlotMappings(FragmentWorkerSlotMappings { + mappings: to_fragment_worker_slot_mapping(&mappings), }), ); } @@ -185,7 +185,7 @@ pub async fn start_serving_vnode_mapping_worker( let (workers, streaming_parallelisms) = fetch_serving_infos(&metadata_manager).await; let (mappings, _) = serving_vnode_mapping.upsert(streaming_parallelisms, &workers); tracing::debug!("Update serving vnode mapping snapshot for fragments {:?}.", mappings.keys()); - notification_manager.notify_frontend_without_version(Operation::Snapshot, Info::ServingParallelUnitMappings(FragmentParallelUnitMappings{ mappings: to_fragment_parallel_unit_mapping(&mappings) })); + notification_manager.notify_frontend_without_version(Operation::Snapshot, Info::ServingWorkerSlotMappings(FragmentWorkerSlotMappings{ mappings: to_fragment_worker_slot_mapping(&mappings) })); } LocalNotification::FragmentMappingsUpsert(fragment_ids) => { if fragment_ids.is_empty() { @@ -195,11 +195,11 @@ pub async fn start_serving_vnode_mapping_worker( let (upserted, failed) = serving_vnode_mapping.upsert(streaming_parallelisms, &workers); if !upserted.is_empty() { tracing::debug!("Update serving vnode mapping for fragments {:?}.", upserted.keys()); - notification_manager.notify_frontend_without_version(Operation::Update, Info::ServingParallelUnitMappings(FragmentParallelUnitMappings{ mappings: to_fragment_parallel_unit_mapping(&upserted) })); + notification_manager.notify_frontend_without_version(Operation::Update, Info::ServingWorkerSlotMappings(FragmentWorkerSlotMappings{ mappings: to_fragment_worker_slot_mapping(&upserted) })); } if !failed.is_empty() { tracing::debug!("Fail to update serving vnode mapping for fragments {:?}.", failed); - notification_manager.notify_frontend_without_version(Operation::Delete, Info::ServingParallelUnitMappings(FragmentParallelUnitMappings{ mappings: to_deleted_fragment_parallel_unit_mapping(&failed)})); + notification_manager.notify_frontend_without_version(Operation::Delete, Info::ServingWorkerSlotMappings(FragmentWorkerSlotMappings{ mappings: to_deleted_fragment_worker_slot_mapping(&failed)})); } } LocalNotification::FragmentMappingsDelete(fragment_ids) => { @@ -208,7 +208,7 @@ pub async fn start_serving_vnode_mapping_worker( } tracing::debug!("Delete serving vnode mapping for fragments {:?}.", fragment_ids); serving_vnode_mapping.remove(&fragment_ids); - notification_manager.notify_frontend_without_version(Operation::Delete, Info::ServingParallelUnitMappings(FragmentParallelUnitMappings{ mappings: to_deleted_fragment_parallel_unit_mapping(&fragment_ids) })); + notification_manager.notify_frontend_without_version(Operation::Delete, Info::ServingWorkerSlotMappings(FragmentWorkerSlotMappings{ mappings: to_deleted_fragment_worker_slot_mapping(&fragment_ids) })); } _ => {} } diff --git a/src/meta/src/storage/etcd_meta_store.rs b/src/meta/src/storage/etcd_meta_store.rs index 8c5f93f9dd9ac..1986dab6247d6 100644 --- a/src/meta/src/storage/etcd_meta_store.rs +++ b/src/meta/src/storage/etcd_meta_store.rs @@ -14,7 +14,6 @@ use std::sync::atomic::{self, AtomicI64}; -use anyhow; use async_trait::async_trait; use etcd_client::{Compare, CompareOp, Error as EtcdError, GetOptions, Txn, TxnOp}; use futures::Future; diff --git a/src/meta/src/storage/transaction.rs b/src/meta/src/storage/transaction.rs index 9a9df92b5f833..daac28f276049 100644 --- a/src/meta/src/storage/transaction.rs +++ b/src/meta/src/storage/transaction.rs @@ -17,9 +17,9 @@ use crate::storage::{ColumnFamily, Key, Value}; /// A `Transaction` executes several writes(aka. operations) to meta store atomically with optional /// preconditions checked. It executes as follow: /// 1. If all `preconditions` are valid, all `operations` are executed; Otherwise no operation -/// is executed. +/// is executed. /// 2. Upon `commit` the transaction, the `TransactionAbort` error will be returned if -/// any precondition was not met in previous step. +/// any precondition was not met in previous step. #[derive(Default)] pub struct Transaction { preconditions: Vec, diff --git a/src/meta/src/stream/mod.rs b/src/meta/src/stream/mod.rs index 8f97f1a4634de..57ca9896257f3 100644 --- a/src/meta/src/stream/mod.rs +++ b/src/meta/src/stream/mod.rs @@ -21,8 +21,36 @@ mod stream_manager; mod test_fragmenter; mod test_scale; +use std::collections::HashMap; + +use risingwave_common::catalog::TableId; +use risingwave_pb::stream_plan::StreamActor; +use risingwave_pb::stream_service::build_actor_info::SubscriptionIds; +use risingwave_pb::stream_service::BuildActorInfo; pub use scale::*; pub use sink::*; pub use source_manager::*; pub use stream_graph::*; pub use stream_manager::*; + +pub(crate) fn to_build_actor_info( + actor: StreamActor, + subscriptions: &HashMap>, + fragment_table_id: TableId, +) -> BuildActorInfo { + BuildActorInfo { + actor: Some(actor), + related_subscriptions: subscriptions + .get(&fragment_table_id) + .into_iter() + .map(|subscriptions| { + ( + fragment_table_id.table_id, + SubscriptionIds { + subscription_ids: subscriptions.keys().cloned().collect(), + }, + ) + }) + .collect(), + } +} diff --git a/src/meta/src/stream/scale.rs b/src/meta/src/stream/scale.rs index a1c0aaa735fe1..e54cb65d8869b 100644 --- a/src/meta/src/stream/scale.rs +++ b/src/meta/src/stream/scale.rs @@ -26,7 +26,7 @@ use itertools::Itertools; use num_integer::Integer; use num_traits::abs; use risingwave_common::bail; -use risingwave_common::buffer::{Bitmap, BitmapBuilder}; +use risingwave_common::bitmap::{Bitmap, BitmapBuilder}; use risingwave_common::catalog::TableId; use risingwave_common::hash::{ActorMapping, ParallelUnitId, VirtualNode}; use risingwave_common::util::iter_util::ZipEqDebug; @@ -34,18 +34,19 @@ use risingwave_meta_model_v2::StreamingParallelism; use risingwave_pb::common::{ ActorInfo, Buffer, ParallelUnit, ParallelUnitMapping, WorkerNode, WorkerType, }; -use risingwave_pb::meta::get_reschedule_plan_request::{Policy, StableResizePolicy}; use risingwave_pb::meta::subscribe_response::{Info, Operation}; use risingwave_pb::meta::table_fragments::actor_status::ActorState; use risingwave_pb::meta::table_fragments::fragment::{ FragmentDistributionType, PbFragmentDistributionType, }; use risingwave_pb::meta::table_fragments::{self, ActorStatus, PbFragment, State}; -use risingwave_pb::meta::FragmentParallelUnitMappings; +use risingwave_pb::meta::FragmentWorkerSlotMappings; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::{ Dispatcher, DispatcherType, FragmentTypeFlag, PbStreamActor, StreamNode, }; +use risingwave_pb::stream_service::build_actor_info::SubscriptionIds; +use risingwave_pb::stream_service::BuildActorInfo; use thiserror_ext::AsReport; use tokio::sync::oneshot::Receiver; use tokio::sync::{oneshot, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -59,8 +60,7 @@ use crate::manager::{ }; use crate::model::{ActorId, DispatcherId, FragmentId, TableFragments, TableParallelism}; use crate::serving::{ - to_deleted_fragment_parallel_unit_mapping, to_fragment_parallel_unit_mapping, - ServingVnodeMapping, + to_deleted_fragment_worker_slot_mapping, to_fragment_worker_slot_mapping, ServingVnodeMapping, }; use crate::storage::{MetaStore, MetaStoreError, MetaStoreRef, Transaction, DEFAULT_COLUMN_FAMILY}; use crate::stream::{GlobalStreamManager, SourceManagerRef}; @@ -236,19 +236,19 @@ impl RescheduleContext { /// The specific process is as follows /// /// 1. Calculate the number of target actors, and calculate the average value and the remainder, and -/// use the average value as expected. +/// use the average value as expected. /// /// 2. Filter out the actor to be removed and the actor to be retained, and sort them from largest -/// to smallest (according to the number of virtual nodes held). +/// to smallest (according to the number of virtual nodes held). /// /// 3. Calculate their balance, 1) For the actors to be removed, the number of virtual nodes per -/// actor is the balance. 2) For retained actors, the number of virtual nodes - expected is the -/// balance. 3) For newly created actors, -expected is the balance (always negative). +/// actor is the balance. 2) For retained actors, the number of virtual nodes - expected is the +/// balance. 3) For newly created actors, -expected is the balance (always negative). /// /// 4. Allocate the remainder, high priority to newly created nodes. /// /// 5. After that, merge removed, retained and created into a queue, with the head of the queue -/// being the source, and move the virtual nodes to the destination at the end of the queue. +/// being the source, and move the virtual nodes to the destination at the end of the queue. /// /// This can handle scale in, scale out, migration, and simultaneous scaling with as much affinity /// as possible. @@ -824,7 +824,7 @@ impl ScaleController { &self, worker_nodes: &HashMap, actor_infos_to_broadcast: BTreeMap, - node_actors_to_create: HashMap>, + node_actors_to_create: HashMap>, broadcast_worker_ids: HashSet, ) -> MetaResult<()> { self.stream_rpc_manager @@ -846,7 +846,7 @@ impl ScaleController { *node_id, stream_actors .iter() - .map(|stream_actor| stream_actor.actor_id) + .map(|stream_actor| stream_actor.actor.as_ref().unwrap().actor_id) .collect_vec(), ) }), @@ -1124,9 +1124,24 @@ impl ScaleController { // After modification, for newly created actors, both upstream and downstream actor ids // have been modified let mut actor_infos_to_broadcast = BTreeMap::new(); - let mut node_actors_to_create: HashMap> = HashMap::new(); + let mut node_actors_to_create: HashMap> = HashMap::new(); let mut broadcast_worker_ids = HashSet::new(); + let subscriptions: HashMap<_, SubscriptionIds> = self + .metadata_manager + .get_mv_depended_subscriptions() + .await? + .iter() + .map(|(table_id, subscriptions)| { + ( + table_id.table_id, + SubscriptionIds { + subscription_ids: subscriptions.keys().cloned().collect(), + }, + ) + }) + .collect(); + for actors_to_create in fragment_actors_to_create.values() { for (new_actor_id, new_parallel_unit_id) in actors_to_create { let new_actor = new_created_actors.get(new_actor_id).unwrap(); @@ -1188,7 +1203,12 @@ impl ScaleController { node_actors_to_create .entry(worker.id) .or_default() - .push(new_actor.clone()); + .push(BuildActorInfo { + actor: Some(new_actor.clone()), + // TODO: may include only the subscriptions related to the table fragment + // of the actor. + related_subscriptions: subscriptions.clone(), + }); broadcast_worker_ids.insert(worker.id); @@ -1703,8 +1723,8 @@ impl ScaleController { .notification_manager() .notify_frontend_without_version( Operation::Update, - Info::ServingParallelUnitMappings(FragmentParallelUnitMappings { - mappings: to_fragment_parallel_unit_mapping(&upserted), + Info::ServingWorkerSlotMappings(FragmentWorkerSlotMappings { + mappings: to_fragment_worker_slot_mapping(&upserted), }), ); } @@ -1717,8 +1737,8 @@ impl ScaleController { .notification_manager() .notify_frontend_without_version( Operation::Delete, - Info::ServingParallelUnitMappings(FragmentParallelUnitMappings { - mappings: to_deleted_fragment_parallel_unit_mapping(&failed), + Info::ServingWorkerSlotMappings(FragmentWorkerSlotMappings { + mappings: to_deleted_fragment_worker_slot_mapping(&failed), }), ); } @@ -2050,351 +2070,6 @@ impl ScaleController { Ok(target_plan) } - pub async fn generate_stable_resize_plan( - &self, - policy: StableResizePolicy, - parallel_unit_hints: Option>>, - ) -> MetaResult> { - let StableResizePolicy { - fragment_worker_changes, - } = policy; - - let mut target_plan = HashMap::with_capacity(fragment_worker_changes.len()); - - let workers = self - .metadata_manager - .list_active_streaming_compute_nodes() - .await?; - - let unschedulable_worker_ids = Self::filter_unschedulable_workers(&workers); - - for changes in fragment_worker_changes.values() { - for worker_id in &changes.include_worker_ids { - if unschedulable_worker_ids.contains(worker_id) { - bail!("Cannot include unscheduable worker {}", worker_id) - } - } - } - - let worker_parallel_units = workers - .iter() - .map(|worker| { - ( - worker.id, - worker - .parallel_units - .iter() - .map(|parallel_unit| parallel_unit.id as ParallelUnitId) - .collect::>(), - ) - }) - .collect::>(); - - // FIXME: only need actor id and dispatcher info, avoid clone it. - let mut actor_map = HashMap::new(); - let mut actor_status = HashMap::new(); - // FIXME: only need fragment distribution info, should avoid clone it. - let mut fragment_map = HashMap::new(); - let mut fragment_parallelism = HashMap::new(); - - // We are reusing code for the metadata manager of both V1 and V2, which will be deprecated in the future. - fn fulfill_index_by_table_fragments_ref( - actor_map: &mut HashMap, - actor_status: &mut HashMap, - fragment_map: &mut HashMap, - fragment_parallelism: &mut HashMap, - table_fragments: &TableFragments, - ) { - for (fragment_id, fragment) in &table_fragments.fragments { - for actor in &fragment.actors { - actor_map.insert(actor.actor_id, CustomActorInfo::from(actor)); - } - - fragment_map.insert(*fragment_id, CustomFragmentInfo::from(fragment)); - - fragment_parallelism.insert(*fragment_id, table_fragments.assigned_parallelism); - } - - actor_status.extend(table_fragments.actor_status.clone()); - } - - match &self.metadata_manager { - MetadataManager::V1(mgr) => { - let guard = mgr.fragment_manager.get_fragment_read_guard().await; - - for table_fragments in guard.table_fragments().values() { - fulfill_index_by_table_fragments_ref( - &mut actor_map, - &mut actor_status, - &mut fragment_map, - &mut fragment_parallelism, - table_fragments, - ); - } - } - MetadataManager::V2(_) => { - let all_table_fragments = self.list_all_table_fragments().await?; - - for table_fragments in &all_table_fragments { - fulfill_index_by_table_fragments_ref( - &mut actor_map, - &mut actor_status, - &mut fragment_map, - &mut fragment_parallelism, - table_fragments, - ); - } - } - }; - - let mut no_shuffle_source_fragment_ids = HashSet::new(); - let mut no_shuffle_target_fragment_ids = HashSet::new(); - - Self::build_no_shuffle_relation_index( - &actor_map, - &mut no_shuffle_source_fragment_ids, - &mut no_shuffle_target_fragment_ids, - ); - - let mut fragment_dispatcher_map = HashMap::new(); - Self::build_fragment_dispatcher_index(&actor_map, &mut fragment_dispatcher_map); - - #[derive(PartialEq, Eq, Clone)] - struct WorkerChanges { - include_worker_ids: BTreeSet, - exclude_worker_ids: BTreeSet, - target_parallelism: Option, - target_parallelism_per_worker: Option, - } - - let mut fragment_worker_changes: HashMap<_, _> = fragment_worker_changes - .into_iter() - .map(|(fragment_id, changes)| { - ( - fragment_id as FragmentId, - WorkerChanges { - include_worker_ids: changes.include_worker_ids.into_iter().collect(), - exclude_worker_ids: changes.exclude_worker_ids.into_iter().collect(), - target_parallelism: changes.target_parallelism.map(|p| p as usize), - target_parallelism_per_worker: changes - .target_parallelism_per_worker - .map(|p| p as usize), - }, - ) - }) - .collect(); - - Self::resolve_no_shuffle_upstream_fragments( - &mut fragment_worker_changes, - &fragment_map, - &no_shuffle_source_fragment_ids, - &no_shuffle_target_fragment_ids, - )?; - - for ( - fragment_id, - WorkerChanges { - include_worker_ids, - exclude_worker_ids, - target_parallelism, - target_parallelism_per_worker, - }, - ) in fragment_worker_changes - { - let fragment = match fragment_map.get(&fragment_id) { - None => bail!("Fragment id {} not found", fragment_id), - Some(fragment) => fragment, - }; - - let intersection_ids = include_worker_ids - .intersection(&exclude_worker_ids) - .collect_vec(); - - if !intersection_ids.is_empty() { - bail!( - "Include worker ids {:?} and exclude worker ids {:?} have intersection {:?}", - include_worker_ids, - exclude_worker_ids, - intersection_ids - ); - } - - for worker_id in include_worker_ids.iter().chain(exclude_worker_ids.iter()) { - if !worker_parallel_units.contains_key(worker_id) - && !parallel_unit_hints - .as_ref() - .map(|hints| hints.contains_key(worker_id)) - .unwrap_or(false) - { - bail!("Worker id {} not found", worker_id); - } - } - - let fragment_parallel_unit_ids: BTreeSet<_> = fragment - .actors - .iter() - .map(|actor| { - actor_status - .get(&actor.actor_id) - .and_then(|status| status.parallel_unit.clone()) - .unwrap() - .id as ParallelUnitId - }) - .collect(); - - let worker_to_parallel_unit_ids = |worker_ids: &BTreeSet| { - worker_ids - .iter() - .flat_map(|worker_id| { - worker_parallel_units - .get(worker_id) - .or_else(|| { - parallel_unit_hints - .as_ref() - .and_then(|hints| hints.get(worker_id)) - }) - .expect("worker id should be valid") - }) - .cloned() - .collect_vec() - }; - - let include_worker_parallel_unit_ids = worker_to_parallel_unit_ids(&include_worker_ids); - let exclude_worker_parallel_unit_ids = worker_to_parallel_unit_ids(&exclude_worker_ids); - - fn refilter_parallel_unit_id_by_target_parallelism( - worker_parallel_units: &HashMap>, - include_worker_ids: &BTreeSet, - include_worker_parallel_unit_ids: &[ParallelUnitId], - target_parallel_unit_ids: &mut BTreeSet, - target_parallelism_per_worker: usize, - ) { - let limited_worker_parallel_unit_ids = include_worker_ids - .iter() - .flat_map(|worker_id| { - worker_parallel_units - .get(worker_id) - .cloned() - .unwrap() - .into_iter() - .sorted() - .take(target_parallelism_per_worker) - }) - .collect_vec(); - - // remove all the parallel units in the limited workers - target_parallel_unit_ids - .retain(|id| !include_worker_parallel_unit_ids.contains(id)); - - // then we re-add the limited parallel units from the limited workers - target_parallel_unit_ids.extend(limited_worker_parallel_unit_ids.into_iter()); - } - match fragment.distribution_type() { - FragmentDistributionType::Unspecified => unreachable!(), - FragmentDistributionType::Single => { - let single_parallel_unit_id = - fragment_parallel_unit_ids.iter().exactly_one().unwrap(); - - let mut target_parallel_unit_ids: BTreeSet<_> = worker_parallel_units - .keys() - .filter(|id| !unschedulable_worker_ids.contains(*id)) - .filter(|id| !exclude_worker_ids.contains(*id)) - .flat_map(|id| worker_parallel_units.get(id).cloned().unwrap()) - .collect(); - - if let Some(target_parallelism_per_worker) = target_parallelism_per_worker { - refilter_parallel_unit_id_by_target_parallelism( - &worker_parallel_units, - &include_worker_ids, - &include_worker_parallel_unit_ids, - &mut target_parallel_unit_ids, - target_parallelism_per_worker, - ); - } - - if target_parallel_unit_ids.is_empty() { - bail!( - "No schedulable ParallelUnits available for single distribution fragment {}", - fragment_id - ); - } - - if !target_parallel_unit_ids.contains(single_parallel_unit_id) { - let sorted_target_parallel_unit_ids = - target_parallel_unit_ids.into_iter().sorted().collect_vec(); - - let chosen_target_parallel_unit_id = sorted_target_parallel_unit_ids - [fragment_id as usize % sorted_target_parallel_unit_ids.len()]; - - target_plan.insert( - fragment_id, - ParallelUnitReschedule { - added_parallel_units: BTreeSet::from([ - chosen_target_parallel_unit_id, - ]), - removed_parallel_units: BTreeSet::from([*single_parallel_unit_id]), - }, - ); - } - } - FragmentDistributionType::Hash => { - let mut target_parallel_unit_ids: BTreeSet<_> = - fragment_parallel_unit_ids.clone(); - target_parallel_unit_ids.extend(include_worker_parallel_unit_ids.iter()); - target_parallel_unit_ids - .retain(|id| !exclude_worker_parallel_unit_ids.contains(id)); - - if target_parallel_unit_ids.is_empty() { - bail!( - "No schedulable ParallelUnits available for fragment {}", - fragment_id - ); - } - - match (target_parallelism, target_parallelism_per_worker) { - (Some(_), Some(_)) => { - bail!("Cannot specify both target parallelism and target parallelism per worker"); - } - (Some(target_parallelism), _) => { - if target_parallel_unit_ids.len() < target_parallelism { - bail!("Target parallelism {} is greater than schedulable ParallelUnits {}", target_parallelism, target_parallel_unit_ids.len()); - } - - target_parallel_unit_ids = target_parallel_unit_ids - .into_iter() - .take(target_parallelism) - .collect(); - } - (_, Some(target_parallelism_per_worker)) => { - refilter_parallel_unit_id_by_target_parallelism( - &worker_parallel_units, - &include_worker_ids, - &include_worker_parallel_unit_ids, - &mut target_parallel_unit_ids, - target_parallelism_per_worker, - ); - } - _ => {} - } - - target_plan.insert( - fragment_id, - Self::diff_parallel_unit_change( - &fragment_parallel_unit_ids, - &target_parallel_unit_ids, - ), - ); - } - } - } - - target_plan.retain(|_, plan| { - !(plan.added_parallel_units.is_empty() && plan.removed_parallel_units.is_empty()) - }); - - Ok(target_plan) - } - fn filter_unschedulable_workers(workers: &[WorkerNode]) -> HashSet { workers .iter() @@ -2429,17 +2104,6 @@ impl ScaleController { } } - pub async fn get_reschedule_plan( - &self, - policy: Policy, - ) -> MetaResult> { - match policy { - Policy::StableResizePolicy(resize) => { - self.generate_stable_resize_plan(resize, None).await - } - } - } - pub fn build_no_shuffle_relation_index( actor_map: &HashMap, no_shuffle_source_fragment_ids: &mut HashSet, @@ -2504,9 +2168,7 @@ impl ScaleController { // We trace the upstreams of each downstream under the hierarchy until we reach the top // for every no_shuffle relation. while let Some(fragment_id) = queue.pop_front() { - if !no_shuffle_target_fragment_ids.contains(&fragment_id) - && !no_shuffle_source_fragment_ids.contains(&fragment_id) - { + if !no_shuffle_target_fragment_ids.contains(&fragment_id) { continue; } @@ -2573,9 +2235,7 @@ impl ScaleController { // We trace the upstreams of each downstream under the hierarchy until we reach the top // for every no_shuffle relation. while let Some(fragment_id) = queue.pop_front() { - if !no_shuffle_target_fragment_ids.contains(&fragment_id) - && !no_shuffle_source_fragment_ids.contains(&fragment_id) - { + if !no_shuffle_target_fragment_ids.contains(&fragment_id) { continue; } diff --git a/src/meta/src/stream/stream_graph/actor.rs b/src/meta/src/stream/stream_graph/actor.rs index 80004aa48eaa7..5d6b110ecdd05 100644 --- a/src/meta/src/stream/stream_graph/actor.rs +++ b/src/meta/src/stream/stream_graph/actor.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use assert_matches::assert_matches; use itertools::Itertools; use risingwave_common::bail; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_common::hash::{ActorId, ActorMapping, ParallelUnitId}; use risingwave_common::util::iter_util::ZipEqFast; use risingwave_pb::meta::table_fragments::Fragment; @@ -121,7 +121,7 @@ impl ActorBuilder { /// /// During this process, the following things will be done: /// 1. Replace the logical `Exchange` in node's input with `Merge`, which can be executed on the - /// compute nodes. + /// compute nodes. /// 2. Fill the upstream mview info of the `Merge` node under the other "leaf" nodes. fn rewrite(&self) -> MetaResult { self.rewrite_inner(&self.nodes, 0) diff --git a/src/meta/src/stream/stream_graph/fragment.rs b/src/meta/src/stream/stream_graph/fragment.rs index af9e233fb2984..47bf446f1555d 100644 --- a/src/meta/src/stream/stream_graph/fragment.rs +++ b/src/meta/src/stream/stream_graph/fragment.rs @@ -148,11 +148,6 @@ impl BuildingFragment { has_table = true; } - NodeBody::Subscription(subscription_node) => { - subscription_node.subscription_catalog.as_mut().unwrap().id = table_id; - - has_table = true; - } NodeBody::Dml(dml_node) => { dml_node.table_id = table_id; dml_node.table_version_id = job.table_version_id().unwrap(); @@ -611,6 +606,27 @@ impl CompleteStreamFragmentGraph { ) } + /// For replacing an existing table based on shared cdc source + pub fn with_upstreams_and_downstreams( + graph: StreamFragmentGraph, + upstream_root_fragments: HashMap, + original_table_fragment_id: FragmentId, + downstream_fragments: Vec<(DispatchStrategy, Fragment)>, + ddl_type: DdlType, + ) -> MetaResult { + Self::build_helper( + graph, + Some(FragmentGraphUpstreamContext { + upstream_root_fragments, + }), + Some(FragmentGraphDownstreamContext { + original_table_fragment_id, + downstream_fragments, + }), + ddl_type, + ) + } + /// The core logic of building a [`CompleteStreamFragmentGraph`], i.e., adding extra upstream/downstream fragments. fn build_helper( mut graph: StreamFragmentGraph, @@ -666,10 +682,7 @@ impl CompleteStreamFragmentGraph { (source_job_id, edge) } - DdlType::MaterializedView - | DdlType::Sink - | DdlType::Index - | DdlType::Subscription => { + DdlType::MaterializedView | DdlType::Sink | DdlType::Index => { // handle MV on MV/Source // Build the extra edges between the upstream `Materialize` and the downstream `StreamScan` diff --git a/src/meta/src/stream/stream_manager.rs b/src/meta/src/stream/stream_manager.rs index 6a801932785c7..a7ba22b914661 100644 --- a/src/meta/src/stream/stream_manager.rs +++ b/src/meta/src/stream/stream_manager.rs @@ -19,7 +19,7 @@ use futures::future::join_all; use itertools::Itertools; use risingwave_common::catalog::TableId; use risingwave_meta_model_v2::ObjectId; -use risingwave_pb::catalog::{CreateType, Table}; +use risingwave_pb::catalog::{CreateType, Subscription, Table}; use risingwave_pb::stream_plan::update_mutation::MergeUpdate; use risingwave_pb::stream_plan::Dispatcher; use thiserror_ext::AsReport; @@ -30,8 +30,8 @@ use tracing::Instrument; use super::{Locations, RescheduleOptions, ScaleControllerRef, TableResizePolicy}; use crate::barrier::{BarrierScheduler, Command, ReplaceTablePlan, StreamRpcManager}; use crate::manager::{DdlType, MetaSrvEnv, MetadataManager, StreamingJob}; -use crate::model::{ActorId, MetadataModel, TableFragments, TableParallelism}; -use crate::stream::SourceManagerRef; +use crate::model::{ActorId, FragmentId, MetadataModel, TableFragments, TableParallelism}; +use crate::stream::{to_build_actor_info, SourceManagerRef}; use crate::{MetaError, MetaResult}; pub type GlobalStreamManagerRef = Arc; @@ -219,9 +219,9 @@ impl GlobalStreamManager { /// Create streaming job, it works as follows: /// /// 1. Broadcast the actor info based on the scheduling result in the context, build the hanging - /// channels in upstream worker nodes. + /// channels in upstream worker nodes. /// 2. (optional) Get the split information of the `StreamSource` via source manager and patch - /// actors. + /// actors. /// 3. Notify related worker nodes to update and build the actors. /// 4. Store related meta data. pub async fn create_streaming_job( @@ -346,6 +346,10 @@ impl GlobalStreamManager { .chain(existing_locations.actor_infos()); let building_worker_actors = building_locations.worker_actors(); + let subscriptions = self + .metadata_manager + .get_mv_depended_subscriptions() + .await?; // We send RPC request in two stages. // The first stage does 2 things: broadcast actor info, and send local actor ids to @@ -359,7 +363,13 @@ impl GlobalStreamManager { building_worker_actors.iter().map(|(worker_id, actors)| { let stream_actors = actors .iter() - .map(|actor_id| actor_map[actor_id].clone()) + .map(|actor_id| { + to_build_actor_info( + actor_map[actor_id].clone(), + &subscriptions, + table_fragments.table_id(), + ) + }) .collect::>(); (*worker_id, stream_actors) }), @@ -540,6 +550,7 @@ impl GlobalStreamManager { removed_actors: Vec, streaming_job_ids: Vec, state_table_ids: Vec, + fragment_ids: HashSet, ) { if !removed_actors.is_empty() || !streaming_job_ids.is_empty() @@ -549,14 +560,11 @@ impl GlobalStreamManager { .barrier_scheduler .run_command(Command::DropStreamingJobs { actors: removed_actors, - unregistered_table_fragment_ids: streaming_job_ids - .into_iter() - .map(|job_id| TableId::new(job_id as _)) - .collect(), unregistered_state_table_ids: state_table_ids .into_iter() .map(|table_id| TableId::new(table_id as _)) .collect(), + unregistered_fragment_ids: fragment_ids, }) .await .inspect_err(|err| { @@ -591,11 +599,14 @@ impl GlobalStreamManager { .barrier_scheduler .run_command(Command::DropStreamingJobs { actors: dropped_actors, - unregistered_table_fragment_ids: table_ids.into_iter().collect(), unregistered_state_table_ids: unregister_table_ids .into_iter() .map(TableId::new) .collect(), + unregistered_fragment_ids: table_fragments_vec + .iter() + .flat_map(|fragments| fragments.fragments.keys().cloned()) + .collect(), }) .await .inspect_err(|err| { @@ -645,7 +656,7 @@ impl GlobalStreamManager { )))?; } if let MetadataManager::V1(mgr) = &self.metadata_manager { - mgr.catalog_manager.cancel_create_table_procedure(id.into(), fragment.internal_table_ids()).await?; + mgr.catalog_manager.cancel_create_materialized_view_procedure(id.into(), fragment.internal_table_ids()).await?; } self.barrier_scheduler @@ -731,17 +742,49 @@ impl GlobalStreamManager { Ok(()) } + + // Dont need add actor, just send a command + pub async fn create_subscription( + self: &Arc, + subscription: &Subscription, + ) -> MetaResult<()> { + let command = Command::CreateSubscription { + subscription_id: subscription.id, + upstream_mv_table_id: TableId::new(subscription.dependent_table_id), + retention_second: subscription.retention_seconds, + }; + + tracing::debug!("sending Command::CreateSubscription"); + self.barrier_scheduler.run_command(command).await?; + Ok(()) + } + + // Dont need add actor, just send a command + pub async fn drop_subscription(self: &Arc, subscription_id: u32, table_id: u32) { + let command = Command::DropSubscription { + subscription_id, + upstream_mv_table_id: TableId::new(table_id), + }; + + tracing::debug!("sending Command::DropSubscription"); + let _ = self + .barrier_scheduler + .run_command(command) + .await + .inspect_err(|err| { + tracing::error!(error = ?err.as_report(), "failed to run drop command"); + }); + } } #[cfg(test)] mod tests { - use std::collections::{BTreeMap, HashMap, HashSet}; + use std::collections::BTreeMap; use std::net::SocketAddr; - use std::sync::{Arc, Mutex}; + use std::sync::Mutex; use std::time::Duration; use futures::{Stream, TryStreamExt}; - use risingwave_common::catalog::TableId; use risingwave_common::hash::ParallelUnitMapping; use risingwave_common::system_param::reader::SystemParamsRead; use risingwave_pb::common::{HostAddress, WorkerType}; @@ -754,10 +797,7 @@ mod tests { StreamService, StreamServiceServer, }; use risingwave_pb::stream_service::streaming_control_stream_response::InitResponse; - use risingwave_pb::stream_service::{ - BroadcastActorInfoTableResponse, BuildActorsResponse, DropActorsRequest, - DropActorsResponse, UpdateActorsResponse, *, - }; + use risingwave_pb::stream_service::*; use tokio::spawn; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::oneshot::Sender; @@ -769,14 +809,13 @@ mod tests { use tonic::{Request, Response, Status, Streaming}; use super::*; - use crate::barrier::{GlobalBarrierManager, StreamRpcManager}; + use crate::barrier::GlobalBarrierManager; use crate::hummock::{CompactorManager, HummockManager}; use crate::manager::sink_coordination::SinkCoordinatorManager; use crate::manager::{ CatalogManager, CatalogManagerRef, ClusterManager, FragmentManager, FragmentManagerRef, - MetaSrvEnv, RelationIdEnum, StreamingClusterInfo, + RelationIdEnum, StreamingClusterInfo, }; - use crate::model::{ActorId, FragmentId}; use crate::rpc::ddl_controller::DropMode; use crate::rpc::metrics::MetaMetrics; use crate::stream::{ScaleController, SourceManager}; @@ -804,6 +843,7 @@ mod tests { let req = request.into_inner(); let mut guard = self.inner.actor_streams.lock().unwrap(); for actor in req.get_actors() { + let actor = actor.actor.as_ref().unwrap(); guard.insert(actor.get_actor_id(), actor.clone()); } @@ -1088,7 +1128,7 @@ mod tests { }; self.catalog_manager - .start_create_table_procedure(&table, vec![]) + .start_create_table_procedure(&table) .await?; self.fragment_manager .start_create_table_fragments(table_fragments.clone()) @@ -1097,7 +1137,7 @@ mod tests { .create_streaming_job(table_fragments, ctx) .await?; self.catalog_manager - .finish_create_table_procedure(vec![], table) + .finish_create_materialized_view_procedure(vec![], table) .await?; Ok(()) } diff --git a/src/meta/src/stream/test_fragmenter.rs b/src/meta/src/stream/test_fragmenter.rs index c0fc385ad96fb..510011e69eb88 100644 --- a/src/meta/src/stream/test_fragmenter.rs +++ b/src/meta/src/stream/test_fragmenter.rs @@ -74,6 +74,7 @@ fn make_sum_aggcall(idx: u32) -> AggCall { order_by: vec![], filter: None, direct_args: vec![], + udf: None, } } @@ -344,9 +345,7 @@ fn make_stream_fragments() -> Vec { make_inputref(0), make_inputref(1), ], - watermark_input_cols: vec![], - watermark_output_cols: vec![], - nondecreasing_exprs: vec![], + ..Default::default() })), fields: vec![], // TODO: fill this later input: vec![simple_agg_node_1], @@ -475,7 +474,13 @@ async fn test_graph_builder() -> MetaResult<()> { let table_fragments = TableFragments::for_test(TableId::default(), graph); let actors = table_fragments.actors(); - let barrier_inject_actor_ids = table_fragments.barrier_inject_actor_ids(); + let barrier_inject_actor_ids = table_fragments + .fragments + .values() + .filter(|fragment| TableFragments::is_injectable(fragment.fragment_type_mask)) + .flat_map(|fragment| fragment.actors.iter().map(|actor| actor.actor_id)) + .sorted() + .collect_vec(); let mview_actor_ids = table_fragments.mview_actor_ids(); assert_eq!(actors.len(), 9); diff --git a/src/meta/src/stream/test_scale.rs b/src/meta/src/stream/test_scale.rs index 73d59ff52f2f4..636ca8e1af5e8 100644 --- a/src/meta/src/stream/test_scale.rs +++ b/src/meta/src/stream/test_scale.rs @@ -18,7 +18,7 @@ mod tests { use itertools::Itertools; use maplit::btreeset; - use risingwave_common::buffer::Bitmap; + use risingwave_common::bitmap::Bitmap; use risingwave_common::hash::{ActorMapping, ParallelUnitId, ParallelUnitMapping, VirtualNode}; use risingwave_pb::common::ParallelUnit; diff --git a/src/meta/src/telemetry.rs b/src/meta/src/telemetry.rs index ece7aec5d7a2d..27456289aa3d3 100644 --- a/src/meta/src/telemetry.rs +++ b/src/meta/src/telemetry.rs @@ -17,10 +17,12 @@ use risingwave_common::config::MetaBackend; use risingwave_common::telemetry::pb_compatible::TelemetryToProtobuf; use risingwave_common::telemetry::report::{TelemetryInfoFetcher, TelemetryReportCreator}; use risingwave_common::telemetry::{ - current_timestamp, SystemData, TelemetryNodeType, TelemetryReportBase, TelemetryResult, + current_timestamp, telemetry_cluster_type_from_env_var, SystemData, TelemetryNodeType, + TelemetryReportBase, TelemetryResult, }; use risingwave_common::{GIT_SHA, RW_VERSION}; use risingwave_pb::common::WorkerType; +use risingwave_pb::telemetry::PbTelemetryClusterType; use serde::{Deserialize, Serialize}; use thiserror_ext::AsReport; @@ -66,6 +68,10 @@ pub struct MetaTelemetryReport { meta_backend: MetaBackend, rw_version: RwVersion, job_desc: Vec, + + // Get the ENV from key `TELEMETRY_CLUSTER_TYPE` + cluster_type: PbTelemetryClusterType, + object_store_media_type: &'static str, } impl From for risingwave_pb::telemetry::StreamJobDesc { @@ -94,7 +100,10 @@ impl TelemetryToProtobuf for MetaTelemetryReport { meta_backend: match self.meta_backend { MetaBackend::Etcd => risingwave_pb::telemetry::MetaBackend::Etcd as i32, MetaBackend::Mem => risingwave_pb::telemetry::MetaBackend::Memory as i32, - MetaBackend::Sql => risingwave_pb::telemetry::MetaBackend::Rdb as i32, + MetaBackend::Sql + | MetaBackend::Sqlite + | MetaBackend::Postgres + | MetaBackend::Mysql => risingwave_pb::telemetry::MetaBackend::Rdb as i32, }, node_count: Some(risingwave_pb::telemetry::NodeCount { meta: self.node_count.meta_count as u32, @@ -108,6 +117,8 @@ impl TelemetryToProtobuf for MetaTelemetryReport { }), stream_job_count: self.streaming_job_count as u32, stream_jobs: self.job_desc.into_iter().map(|job| job.into()).collect(), + cluster_type: self.cluster_type as i32, + object_store_media_type: self.object_store_media_type.to_string(), }; pb_report.encode_to_vec() } @@ -134,13 +145,19 @@ impl TelemetryInfoFetcher for MetaTelemetryInfoFetcher { pub struct MetaReportCreator { metadata_manager: MetadataManager, meta_backend: MetaBackend, + object_store_media_type: &'static str, } impl MetaReportCreator { - pub fn new(metadata_manager: MetadataManager, meta_backend: MetaBackend) -> Self { + pub fn new( + metadata_manager: MetadataManager, + meta_backend: MetaBackend, + object_store_media_type: &'static str, + ) -> Self { Self { metadata_manager, meta_backend, + object_store_media_type, } } } @@ -194,6 +211,8 @@ impl TelemetryReportCreator for MetaReportCreator { streaming_job_count, meta_backend: self.meta_backend, job_desc: stream_job_desc, + cluster_type: telemetry_cluster_type_from_env_var(), + object_store_media_type: self.object_store_media_type, }) } @@ -208,6 +227,7 @@ mod test { use risingwave_common::telemetry::{ current_timestamp, SystemData, TelemetryNodeType, TelemetryReportBase, }; + use risingwave_pb::telemetry::PbTelemetryClusterType; use crate::telemetry::{MetaTelemetryReport, NodeCount, RwVersion}; @@ -219,7 +239,7 @@ mod test { use crate::telemetry::TELEMETRY_META_REPORT_TYPE; - // we don't call `create_report` here because it rely on the metadata manager + // we don't call `create_report` here because it relies on the metadata manager let report = MetaTelemetryReport { base: TelemetryReportBase { tracking_id: "7d45669c-08c7-4571-ae3d-d3a3e70a2f7e".to_owned(), @@ -243,6 +263,8 @@ mod test { git_sha: "git_sha".to_owned(), }, job_desc: vec![], + cluster_type: PbTelemetryClusterType::Unspecified, + object_store_media_type: "s3", }; let pb_bytes = report.to_pb_bytes(); diff --git a/src/object_store/Cargo.toml b/src/object_store/Cargo.toml index e4d573e8f4d2d..38bef42305792 100644 --- a/src/object_store/Cargo.toml +++ b/src/object_store/Cargo.toml @@ -8,6 +8,9 @@ license = { workspace = true } repository = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lints] +workspace = true + [dependencies] async-trait = "0.1" await-tree = { workspace = true } @@ -23,15 +26,28 @@ crc32fast = "1" either = "1" fail = "0.5" futures = { version = "0.3", default-features = false, features = ["alloc"] } -hyper = { version = "0.14", features = ["tcp", "client"] } # required by aws sdk +hyper = { version = "0.14", features = ["tcp", "client"] } # required by aws sdk hyper-rustls = { version = "0.24.2", features = ["webpki-roots"] } hyper-tls = "0.5.0" itertools = { workspace = true } -madsim = "0.2.22" -opendal = "0.45.1" +madsim = "0.2.27" +opendal = { version = "0.47", features = [ + "executors-tokio", + "services-azblob", + "services-fs", + "services-gcs", + "services-memory", + "services-obs", + "services-oss", + "services-s3", + "services-webhdfs", + "services-azfile", + # "service-hdfs", +] } prometheus = { version = "0.13", features = ["process"] } -reqwest = "0.11" # required by opendal +reqwest = "0.12.2" # required by opendal risingwave_common = { workspace = true } +risingwave_jni_core = { workspace = true, optional = true } rustls = "0.23.5" spin = "0.9" thiserror = "1" @@ -49,4 +65,4 @@ tracing = "0.1" # normal = ["workspace-hack"] # [features] -# hdfs-backend = ["opendal/services-hdfs"] +# hdfs-backend = ["opendal/services-hdfs", "dep:risingwave_jni_core"] diff --git a/src/object_store/src/object/error.rs b/src/object_store/src/object/error.rs index d4e8e0bebe155..aa79f53c4c065 100644 --- a/src/object_store/src/object/error.rs +++ b/src/object_store/src/object/error.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::io; -use std::marker::{Send, Sync}; use aws_sdk_s3::operation::get_object::GetObjectError; use aws_sdk_s3::operation::head_object::HeadObjectError; @@ -27,8 +26,13 @@ use tokio::sync::oneshot::error::RecvError; #[derive(Error, thiserror_ext::ReportDebug, thiserror_ext::Box, thiserror_ext::Construct)] #[thiserror_ext(newtype(name = ObjectError, backtrace))] pub enum ObjectErrorInner { - #[error("s3 error: {0}")] - S3(#[source] BoxedError), + #[error("s3 error: {inner}")] + S3 { + // TODO: remove this after switch s3 backend to opendal + should_retry: bool, + #[source] + inner: BoxedError, + }, #[error("disk error: {msg}")] Disk { msg: String, @@ -45,6 +49,9 @@ pub enum ObjectErrorInner { #[cfg(madsim)] #[error(transparent)] Sim(#[from] crate::object::sim::SimError), + + #[error("Timeout error: {0}")] + Timeout(String), } impl ObjectError { @@ -55,17 +62,20 @@ impl ObjectError { /// Tells whether the error indicates the target object is not found. pub fn is_object_not_found_error(&self) -> bool { match self.inner() { - ObjectErrorInner::S3(e) => { - if let Some(aws_smithy_runtime_api::client::result::SdkError::ServiceError(err)) = e - .downcast_ref:: { + if let Some(aws_smithy_runtime_api::client::result::SdkError::ServiceError(err)) = + inner.downcast_ref::, >>() { return matches!(err.err(), GetObjectError::NoSuchKey(_)); } - if let Some(aws_smithy_runtime_api::client::result::SdkError::ServiceError(err)) = e - .downcast_ref::, >>() @@ -90,6 +100,21 @@ impl ObjectError { }; false } + + pub fn should_retry(&self) -> bool { + match self.inner() { + ObjectErrorInner::S3 { + inner: _, + should_retry, + } => *should_retry, + + ObjectErrorInner::Opendal(e) => e.is_temporary(), + + ObjectErrorInner::Timeout(_) => true, + + _ => false, + } + } } impl From> for ObjectError @@ -98,7 +123,11 @@ where R: Send + Sync + 'static + std::fmt::Debug, { fn from(e: aws_smithy_runtime_api::client::result::SdkError) -> Self { - ObjectErrorInner::S3(e.into()).into() + ObjectErrorInner::S3 { + inner: e.into(), + should_retry: false, + } + .into() } } @@ -110,7 +139,11 @@ impl From for ObjectError { impl From for ObjectError { fn from(e: ByteStreamError) -> Self { - ObjectErrorInner::Internal(e.to_report_string()).into() + ObjectErrorInner::S3 { + inner: e.into(), + should_retry: true, + } + .into() } } diff --git a/src/object_store/src/object/mem.rs b/src/object_store/src/object/mem.rs index 3a1a7ed655e81..270cac3719d61 100644 --- a/src/object_store/src/object/mem.rs +++ b/src/object_store/src/object/mem.rs @@ -28,8 +28,7 @@ use thiserror::Error; use tokio::sync::Mutex; use super::{ - BoxedStreamingUploader, ObjectError, ObjectMetadata, ObjectRangeBounds, ObjectResult, - ObjectStore, StreamingUploader, + ObjectError, ObjectMetadata, ObjectRangeBounds, ObjectResult, ObjectStore, StreamingUploader, }; use crate::object::{ObjectDataStream, ObjectMetadataIter}; @@ -64,7 +63,6 @@ pub struct InMemStreamingUploader { objects: Arc>>, } -#[async_trait::async_trait] impl StreamingUploader for InMemStreamingUploader { async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> { fail_point!("mem_write_bytes_err", |_| Err(ObjectError::internal( @@ -74,7 +72,7 @@ impl StreamingUploader for InMemStreamingUploader { Ok(()) } - async fn finish(self: Box) -> ObjectResult<()> { + async fn finish(self) -> ObjectResult<()> { fail_point!("mem_finish_streaming_upload_err", |_| Err( ObjectError::internal("mem finish streaming upload error") )); @@ -101,7 +99,9 @@ pub struct InMemObjectStore { #[async_trait::async_trait] impl ObjectStore for InMemObjectStore { - fn get_object_prefix(&self, _obj_id: u64) -> String { + type StreamingUploader = InMemStreamingUploader; + + fn get_object_prefix(&self, _obj_id: u64, _use_new_object_prefix_strategy: bool) -> String { String::default() } @@ -121,12 +121,12 @@ impl ObjectStore for InMemObjectStore { } } - async fn streaming_upload(&self, path: &str) -> ObjectResult { - Ok(Box::new(InMemStreamingUploader { + async fn streaming_upload(&self, path: &str) -> ObjectResult { + Ok(InMemStreamingUploader { path: path.to_string(), buf: BytesMut::new(), objects: self.objects.clone(), - })) + }) } async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult { @@ -307,7 +307,6 @@ impl Stream for InMemObjectIter { #[cfg(test)] mod tests { - use bytes::Bytes; use futures::TryStreamExt; use itertools::enumerate; diff --git a/src/object_store/src/object/mod.rs b/src/object_store/src/object/mod.rs index a623e4b116fd2..46139804c6576 100644 --- a/src/object_store/src/object/mod.rs +++ b/src/object_store/src/object/mod.rs @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(madsim)] pub mod sim; use std::ops::{Range, RangeBounds}; use std::sync::Arc; use std::time::Duration; use bytes::Bytes; -use prometheus::HistogramTimer; pub mod mem; pub use mem::*; @@ -30,7 +28,7 @@ pub use opendal_engine::*; pub mod s3; use await_tree::InstrumentAwait; use futures::stream::BoxStream; -use futures::StreamExt; +use futures::{Future, StreamExt}; pub use risingwave_common::config::ObjectStoreConfig; pub use s3::*; @@ -42,31 +40,16 @@ mod prefix; pub use error::*; use object_metrics::ObjectStoreMetrics; use thiserror_ext::AsReport; +use tokio_retry::strategy::{jitter, ExponentialBackoff}; #[cfg(madsim)] use self::sim::SimObjectStore; pub type ObjectStoreRef = Arc; -pub type ObjectStreamingUploader = MonitoredStreamingUploader; - -type BoxedStreamingUploader = Box; +pub type ObjectStreamingUploader = StreamingUploaderImpl; pub trait ObjectRangeBounds = RangeBounds + Clone + Send + Sync + std::fmt::Debug + 'static; -/// Partitions a set of given paths into two vectors. The first vector contains all local paths, and -/// the second contains all remote paths. -fn partition_object_store_paths(paths: &[String]) -> Vec { - // ToDo: Currently the result is a copy of the input. Would it be worth it to use an in-place - // partition instead? - let mut vec_rem = vec![]; - - for path in paths { - vec_rem.push(path.to_string()); - } - - vec_rem -} - #[derive(Debug, Clone, PartialEq)] pub struct ObjectMetadata { // Full path @@ -76,11 +59,12 @@ pub struct ObjectMetadata { pub total_size: usize, } -#[async_trait::async_trait] pub trait StreamingUploader: Send { + #[expect(async_fn_in_trait)] async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()>; - async fn finish(self: Box) -> ObjectResult<()>; + #[expect(async_fn_in_trait)] + async fn finish(self) -> ObjectResult<()>; fn get_memory_usage(&self) -> u64; } @@ -88,13 +72,14 @@ pub trait StreamingUploader: Send { /// The implementation must be thread-safe. #[async_trait::async_trait] pub trait ObjectStore: Send + Sync { - /// Get the key prefix for object - fn get_object_prefix(&self, obj_id: u64) -> String; + type StreamingUploader: StreamingUploader; + /// Get the key prefix for object, the prefix is determined by the type of object store and `devise_object_prefix`. + fn get_object_prefix(&self, obj_id: u64, use_new_object_prefix_strategy: bool) -> String; /// Uploads the object to `ObjectStore`. async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()>; - async fn streaming_upload(&self, path: &str) -> ObjectResult; + async fn streaming_upload(&self, path: &str) -> ObjectResult; /// If objects are PUT using a multipart upload, it's a good practice to GET them in the same /// part sizes (or at least aligned to part boundaries) for best performance. @@ -123,7 +108,7 @@ pub trait ObjectStore: Send + Sync { fn monitored( self, metrics: Arc, - config: ObjectStoreConfig, + config: Arc, ) -> MonitoredObjectStore where Self: Sized, @@ -134,94 +119,182 @@ pub trait ObjectStore: Send + Sync { async fn list(&self, prefix: &str) -> ObjectResult; fn store_media_type(&self) -> &'static str; + + fn support_streaming_upload(&self) -> bool { + true + } } -pub enum ObjectStoreImpl { - InMem(MonitoredObjectStore), - Opendal(MonitoredObjectStore), - S3(MonitoredObjectStore), - #[cfg(madsim)] - Sim(MonitoredObjectStore), +#[cfg(not(madsim))] +macro_rules! for_all_object_store { + ($macro:ident $($args:tt)*) => { + $macro! { + { + { InMem, InMemObjectStore }, + { Opendal, OpendalObjectStore }, + { S3, S3ObjectStore } + } + $($args)* + } + } } -macro_rules! dispatch_async { - ($object_store:expr, $method_name:ident $(, $args:expr)*) => { - $object_store.$method_name($($args, )*).await +#[cfg(madsim)] +macro_rules! for_all_object_store { + ($macro:ident $($args:tt)*) => { + $macro! { + { + { InMem, InMemObjectStore }, + { Opendal, OpendalObjectStore }, + { S3, S3ObjectStore }, + { Sim, SimObjectStore } + } + $($args)* + } } } -/// This macro routes the object store operation to the real implementation by the `ObjectStoreImpl` -/// enum type and the `path`. -/// -/// Except for `InMem`,the operation should be performed on remote object store. -macro_rules! object_store_impl_method_body { - ($object_store:expr, $method_name:ident, $dispatch_macro:ident, $path:expr $(, $args:expr)*) => { +macro_rules! enum_map { + ( { - let path = $path; - match $object_store { - ObjectStoreImpl::InMem(in_mem) => { - $dispatch_macro!(in_mem, $method_name, path $(, $args)*) - }, - ObjectStoreImpl::Opendal(opendal) => { - $dispatch_macro!(opendal, $method_name, path $(, $args)*) - }, - ObjectStoreImpl::S3(s3) => { - $dispatch_macro!(s3, $method_name, path $(, $args)*) - }, - #[cfg(madsim)] - ObjectStoreImpl::Sim(in_mem) => { - $dispatch_macro!(in_mem, $method_name, path $(, $args)*) + $( + {$variant:ident, $_type_name:ty} + ),* + }, + $object_store:expr, + $var_name:ident, + $func:expr + ) => { + match $object_store { + $( + ObjectStoreEnum::$variant($var_name) => ObjectStoreEnum::$variant({ + $func + }), + )* + } + }; + ($object_store:expr, |$var_name:ident| $func:expr) => { + for_all_object_store! { + enum_map, $object_store, $var_name, $func + } + }; +} + +macro_rules! dispatch_object_store_enum { + ( + { + $( + {$variant:ident, $_type_name:ty} + ),* + }, + $object_store:expr, + $var_name:ident, + $func:expr + ) => { + match $object_store { + $( + ObjectStoreEnum::$variant($var_name) => { + $func }, - } + )* + } + }; + ($object_store:expr, |$var_name:ident| $func:expr) => { + for_all_object_store! { + dispatch_object_store_enum, $object_store, $var_name, $func + } + }; +} +macro_rules! define_object_store_impl { + () => { + for_all_object_store! { + define_object_store_impl } }; + ( + {$( + {$variant:ident, $type_name:ty} + ),*} + ) => { + pub enum ObjectStoreEnum< + $($variant),* + > { + $( + $variant($variant), + )* + } + + pub type ObjectStoreImpl = ObjectStoreEnum< + $( + MonitoredObjectStore<$type_name>, + )* + >; + + pub type StreamingUploaderImpl = ObjectStoreEnum< + $( + MonitoredStreamingUploader<<$type_name as ObjectStore>::StreamingUploader> + ),* + >; + }; } +define_object_store_impl!(); + /// This macro routes the object store operation to the real implementation by the `ObjectStoreImpl` -/// enum type and the `paths`. It is a modification of the macro above to work with a slice of -/// strings instead of just a single one. +/// enum type and the `path`. /// -/// Except for `InMem`, the operation should be performed on remote object store. -macro_rules! object_store_impl_method_body_slice { - ($object_store:expr, $method_name:ident, $dispatch_macro:ident, $paths:expr $(, $args:expr)*) => { +/// Except for `InMem`,the operation should be performed on remote object store. +macro_rules! object_store_impl_method_body { + // with await + ($object_store:expr, $method_name:ident ($($args:expr),*).await) => { { - let paths_rem = partition_object_store_paths($paths); - - match $object_store { - ObjectStoreImpl::InMem(in_mem) => { - $dispatch_macro!(in_mem, $method_name, &paths_rem $(, $args)*) - }, - ObjectStoreImpl::Opendal(opendal) => { - $dispatch_macro!(opendal, $method_name, &paths_rem $(, $args)*) - }, - ObjectStoreImpl::S3(s3) => { - $dispatch_macro!(s3, $method_name, &paths_rem $(, $args)*) - }, - #[cfg(madsim)] - ObjectStoreImpl::Sim(in_mem) => { - $dispatch_macro!(in_mem, $method_name, &paths_rem $(, $args)*) - }, - } + dispatch_object_store_enum! {$object_store, |os| { + os.$method_name($($args),*).await + }} + } + }; + // no await + ($object_store:expr, $method_name:ident ($(, $args:expr)*)) => { + { + dispatch_object_store_enum! {$object_store, |os| { + os.$method_name($($args),*) + }} } }; } +impl StreamingUploaderImpl { + pub async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> { + object_store_impl_method_body!(self, write_bytes(data).await) + } + + pub async fn finish(self) -> ObjectResult<()> { + object_store_impl_method_body!(self, finish().await) + } + + pub fn get_memory_usage(&self) -> u64 { + object_store_impl_method_body!(self, get_memory_usage()) + } +} + impl ObjectStoreImpl { pub async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()> { - object_store_impl_method_body!(self, upload, dispatch_async, path, obj) + object_store_impl_method_body!(self, upload(path, obj).await) } - pub async fn streaming_upload(&self, path: &str) -> ObjectResult { - object_store_impl_method_body!(self, streaming_upload, dispatch_async, path) + pub async fn streaming_upload(&self, path: &str) -> ObjectResult { + Ok(enum_map!(self, |store| { + store.streaming_upload(path).await? + })) } pub async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult { - object_store_impl_method_body!(self, read, dispatch_async, path, range) + object_store_impl_method_body!(self, read(path, range).await) } pub async fn metadata(&self, path: &str) -> ObjectResult { - object_store_impl_method_body!(self, metadata, dispatch_async, path) + object_store_impl_method_body!(self, metadata(path).await) } /// Returns a stream reading the object specified in `path`. If given, the stream starts at the @@ -232,11 +305,11 @@ impl ObjectStoreImpl { path: &str, start_loc: Range, ) -> ObjectResult { - object_store_impl_method_body!(self, streaming_read, dispatch_async, path, start_loc) + object_store_impl_method_body!(self, streaming_read(path, start_loc).await) } pub async fn delete(&self, path: &str) -> ObjectResult<()> { - object_store_impl_method_body!(self, delete, dispatch_async, path) + object_store_impl_method_body!(self, delete(path).await) } /// Deletes the objects with the given paths permanently from the storage. If an object @@ -245,36 +318,25 @@ impl ObjectStoreImpl { /// If a hybrid storage is used, the method will first attempt to delete objects in local /// storage. Only if that is successful, it will remove objects from remote storage. pub async fn delete_objects(&self, paths: &[String]) -> ObjectResult<()> { - object_store_impl_method_body_slice!(self, delete_objects, dispatch_async, paths) + object_store_impl_method_body!(self, delete_objects(paths).await) } pub async fn list(&self, prefix: &str) -> ObjectResult { - object_store_impl_method_body!(self, list, dispatch_async, prefix) + object_store_impl_method_body!(self, list(prefix).await) } - pub fn get_object_prefix(&self, obj_id: u64) -> String { - // FIXME: ObjectStoreImpl lacks flexibility for adding new interface to ObjectStore - // trait. Macro object_store_impl_method_body routes to local or remote only depending on - // the path - match self { - ObjectStoreImpl::InMem(store) => store.inner.get_object_prefix(obj_id), - ObjectStoreImpl::Opendal(store) => store.inner.get_object_prefix(obj_id), - ObjectStoreImpl::S3(store) => store.inner.get_object_prefix(obj_id), - #[cfg(madsim)] - ObjectStoreImpl::Sim(store) => store.inner.get_object_prefix(obj_id), - } + pub fn get_object_prefix(&self, obj_id: u64, use_new_object_prefix_strategy: bool) -> String { + dispatch_object_store_enum!(self, |store| store + .inner + .get_object_prefix(obj_id, use_new_object_prefix_strategy)) } pub fn support_streaming_upload(&self) -> bool { - match self { - ObjectStoreImpl::InMem(_) => true, - ObjectStoreImpl::Opendal(store) => { - store.inner.op.info().native_capability().write_can_multi - } - ObjectStoreImpl::S3(_) => true, - #[cfg(madsim)] - ObjectStoreImpl::Sim(_) => true, - } + dispatch_object_store_enum!(self, |store| store.inner.support_streaming_upload()) + } + + pub fn media_type(&self) -> &'static str { + object_store_impl_method_body!(self, media_type()) } } @@ -303,101 +365,75 @@ fn try_update_failure_metric( /// - `streaming_upload_finish`: The time spent calling `finish`. /// - `failure_count`: `streaming_upload_start`, `streaming_upload_write_bytes`, /// `streaming_upload_finish` -pub struct MonitoredStreamingUploader { - inner: BoxedStreamingUploader, +pub struct MonitoredStreamingUploader { + inner: U, object_store_metrics: Arc, /// Length of data uploaded with this uploader. operation_size: usize, - media_type: &'static str, - streaming_upload_timeout: Option, } -impl MonitoredStreamingUploader { - pub fn new( - media_type: &'static str, - handle: BoxedStreamingUploader, - object_store_metrics: Arc, - streaming_upload_timeout: Option, - ) -> Self { +impl MonitoredStreamingUploader { + pub fn new(handle: U, object_store_metrics: Arc) -> Self { Self { inner: handle, object_store_metrics, operation_size: 0, - media_type, - streaming_upload_timeout, } } } -impl MonitoredStreamingUploader { - pub async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> { - let operation_type = "streaming_upload_write_bytes"; +/// NOTICE: after #16231, streaming uploader implemented via aws-sdk-s3 will maintain metrics internally in s3.rs +/// so `MonitoredStreamingUploader` will only be used when the inner object store is opendal. +impl MonitoredStreamingUploader { + async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> { + let operation_type = OperationType::StreamingUpload; + let operation_type_str = operation_type.as_str(); let data_len = data.len(); + + let res = + // TODO: we should avoid this special case after fully migrating to opeandal for s3. + self.inner + .write_bytes(data) + .verbose_instrument_await(operation_type_str) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); + + // duration metrics is collected and reported inside the specific implementation of the streaming uploader. self.object_store_metrics .write_bytes - .inc_by(data.len() as u64); + .inc_by(data_len as u64); self.object_store_metrics .operation_size - .with_label_values(&[operation_type]) + .with_label_values(&[operation_type_str]) .observe(data_len as f64); - let _timer = self - .object_store_metrics - .operation_latency - .with_label_values(&[self.media_type, operation_type]) - .start_timer(); self.operation_size += data_len; - let future = async { - self.inner - .write_bytes(data) - .verbose_instrument_await("object_store_streaming_upload_write_bytes") - .await - }; - let res = match self.streaming_upload_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) - .await - .unwrap_or_else(|_| { - Err(ObjectError::internal( - "streaming_upload write_bytes timeout", - )) - }), - }; - - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); res } - pub async fn finish(self) -> ObjectResult<()> { - let operation_type = "streaming_upload_finish"; - self.object_store_metrics - .operation_size - .with_label_values(&["streaming_upload"]) - .observe(self.operation_size as f64); - let _timer = self - .object_store_metrics - .operation_latency - .with_label_values(&[self.media_type, operation_type]) - .start_timer(); + async fn finish(self) -> ObjectResult<()> { + let operation_type = OperationType::StreamingUploadFinish; + let operation_type_str = operation_type.as_str(); - let future = async { + let res = + // TODO: we should avoid this special case after fully migrating to opeandal for s3. self.inner .finish() - .verbose_instrument_await("object_store_streaming_upload_finish") - .await - }; - let res = match self.streaming_upload_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) - .await - .unwrap_or_else(|_| Err(ObjectError::internal("streaming_upload finish timeout"))), - }; + .verbose_instrument_await(operation_type_str) + .await; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); + + // duration metrics is collected and reported inside the specific implementation of the streaming uploader. + self.object_store_metrics + .operation_size + .with_label_values(&[operation_type_str]) + .observe(self.operation_size as f64); res } - pub fn get_memory_usage(&self) -> u64 { + fn get_memory_usage(&self) -> u64 { self.inner.get_memory_usage() } } @@ -407,8 +443,8 @@ pub struct MonitoredStreamingReader { object_store_metrics: Arc, operation_size: usize, media_type: &'static str, - timer: Option, streaming_read_timeout: Option, + operation_type_str: &'static str, } impl MonitoredStreamingReader { @@ -418,54 +454,49 @@ impl MonitoredStreamingReader { object_store_metrics: Arc, streaming_read_timeout: Option, ) -> Self { - let operation_type = "streaming_read"; - let timer = object_store_metrics - .operation_latency - .with_label_values(&[media_type, operation_type]) - .start_timer(); Self { inner: handle, object_store_metrics, operation_size: 0, media_type, - timer: Some(timer), streaming_read_timeout, + operation_type_str: OperationType::StreamingRead.as_str(), } } pub async fn read_bytes(&mut self) -> Option> { - let operation_type = "streaming_read_read_bytes"; let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[self.media_type, operation_type]) + .with_label_values(&[self.media_type, self.operation_type_str]) .start_timer(); let future = async { self.inner .next() - .verbose_instrument_await("object_store_streaming_read_read_bytes") + .verbose_instrument_await(self.operation_type_str) .await }; let res = match self.streaming_read_timeout.as_ref() { None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) + Some(timeout_duration) => tokio::time::timeout(*timeout_duration, future) .await .unwrap_or_else(|_| { - Some(Err(ObjectError::internal( - "streaming_read read_bytes timeout", - ))) + Some(Err(ObjectError::timeout(format!( + "Retry attempts exhausted for {}. Please modify {}_attempt_timeout_ms (current={:?}) under [storage.object_store.retry] in the config accordingly if needed.", + self.operation_type_str, self.operation_type_str, timeout_duration.as_millis() + )))) }), }; if let Some(ret) = &res { - try_update_failure_metric(&self.object_store_metrics, ret, operation_type); + try_update_failure_metric(&self.object_store_metrics, ret, self.operation_type_str); } if let Some(Ok(data)) = &res { let data_len = data.len(); self.object_store_metrics.read_bytes.inc_by(data_len as u64); self.object_store_metrics .operation_size - .with_label_values(&[operation_type]) + .with_label_values(&[self.operation_type_str]) .observe(data_len as f64); self.operation_size += data_len; } @@ -475,22 +506,17 @@ impl MonitoredStreamingReader { impl Drop for MonitoredStreamingReader { fn drop(&mut self) { - let operation_type = "streaming_read"; self.object_store_metrics .operation_size - .with_label_values(&[operation_type]) + .with_label_values(&[self.operation_type_str]) .observe(self.operation_size as f64); - self.timer.take().unwrap().observe_duration(); } } pub struct MonitoredObjectStore { inner: OS, object_store_metrics: Arc, - streaming_read_timeout: Option, - streaming_upload_timeout: Option, - read_timeout: Option, - upload_timeout: Option, + config: Arc, } /// Manually dispatch trait methods. @@ -513,19 +539,12 @@ impl MonitoredObjectStore { pub fn new( store: OS, object_store_metrics: Arc, - config: ObjectStoreConfig, + config: Arc, ) -> Self { Self { object_store_metrics, - streaming_read_timeout: Some(Duration::from_millis( - config.object_store_streaming_read_timeout_ms, - )), - streaming_upload_timeout: Some(Duration::from_millis( - config.object_store_streaming_upload_timeout_ms, - )), - read_timeout: Some(Duration::from_millis(config.object_store_read_timeout_ms)), - upload_timeout: Some(Duration::from_millis(config.object_store_upload_timeout_ms)), inner: store, + config, } } @@ -542,85 +561,91 @@ impl MonitoredObjectStore { } pub async fn upload(&self, path: &str, obj: Bytes) -> ObjectResult<()> { - let operation_type = "upload"; + let operation_type = OperationType::Upload; + let operation_type_str = operation_type.as_str(); + self.object_store_metrics .write_bytes .inc_by(obj.len() as u64); self.object_store_metrics .operation_size - .with_label_values(&[operation_type]) + .with_label_values(&[operation_type_str]) .observe(obj.len() as f64); let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[self.media_type(), operation_type]) + .with_label_values(&[self.media_type(), operation_type_str]) .start_timer(); - let future = async { + + let builder = || async { self.inner - .upload(path, obj) - .verbose_instrument_await("object_store_upload") + .upload(path, obj.clone()) + .verbose_instrument_await(operation_type_str) .await }; - let res = match self.upload_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) - .await - .unwrap_or_else(|_| Err(ObjectError::internal("upload timeout"))), - }; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + let res = retry_request( + builder, + &self.config, + operation_type, + self.object_store_metrics.clone(), + ) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); res } - pub async fn streaming_upload(&self, path: &str) -> ObjectResult { - let operation_type = "streaming_upload_start"; + pub async fn streaming_upload( + &self, + path: &str, + ) -> ObjectResult> { + let operation_type = OperationType::StreamingUploadInit; + let operation_type_str = operation_type.as_str(); let media_type = self.media_type(); let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[media_type, operation_type]) + .with_label_values(&[media_type, operation_type_str]) .start_timer(); - let future = async { - self.inner - .streaming_upload(path) - .verbose_instrument_await("object_store_streaming_upload") - .await - }; - let res = match self.streaming_upload_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) - .await - .unwrap_or_else(|_| Err(ObjectError::internal("streaming_upload init timeout"))), - }; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + let res = self + .inner + .streaming_upload(path) + .verbose_instrument_await(operation_type_str) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); + Ok(MonitoredStreamingUploader::new( - media_type, res?, self.object_store_metrics.clone(), - self.streaming_upload_timeout, )) } pub async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult { - let operation_type = "read"; + let operation_type = OperationType::Read; + let operation_type_str = operation_type.as_str(); let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[self.media_type(), operation_type]) + .with_label_values(&[self.media_type(), operation_type_str]) .start_timer(); - let future = async { + + let builder = || async { self.inner - .read(path, range) - .verbose_instrument_await("object_store_read") + .read(path, range.clone()) + .verbose_instrument_await(operation_type_str) .await }; - let res = match self.read_timeout.as_ref() { - None => future.await, - Some(read_timeout) => tokio::time::timeout(*read_timeout, future) - .await - .unwrap_or_else(|_| Err(ObjectError::internal("read timeout"))), - }; + + let res = retry_request( + builder, + &self.config, + operation_type, + self.object_store_metrics.clone(), + ) + .await; if let Err(e) = &res && e.is_object_not_found_error() @@ -629,7 +654,7 @@ impl MonitoredObjectStore { // Some not_found_error is expected, e.g. metadata backup's manifest.json. // This is a quick fix that'll only log error in `try_update_failure_metric` in state store usage. } else { - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); } let data = res?; @@ -638,7 +663,7 @@ impl MonitoredObjectStore { .inc_by(data.len() as u64); self.object_store_metrics .operation_size - .with_label_values(&[operation_type]) + .with_label_values(&[operation_type_str]) .observe(data.len() as f64); Ok(data) } @@ -651,130 +676,152 @@ impl MonitoredObjectStore { path: &str, range: Range, ) -> ObjectResult { - let operation_type = "streaming_read_start"; + let operation_type = OperationType::StreamingReadInit; + let operation_type_str = operation_type.as_str(); let media_type = self.media_type(); let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[media_type, operation_type]) + .with_label_values(&[media_type, operation_type_str]) .start_timer(); - let future = self - .inner - .streaming_read(path, range) - .verbose_instrument_await("object_store_streaming_read"); - let res = match self.streaming_read_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) + + let builder = || async { + self.inner + .streaming_read(path, range.clone()) + .verbose_instrument_await(operation_type_str) .await - .unwrap_or_else(|_| Err(ObjectError::internal("streaming_read init timeout"))), }; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + let res = retry_request( + builder, + &self.config, + operation_type, + self.object_store_metrics.clone(), + ) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); + Ok(MonitoredStreamingReader::new( media_type, res?, self.object_store_metrics.clone(), - self.streaming_read_timeout, + Some(Duration::from_millis( + self.config.retry.streaming_read_attempt_timeout_ms, + )), )) } pub async fn metadata(&self, path: &str) -> ObjectResult { - let operation_type = "metadata"; + let operation_type = OperationType::Metadata; + let operation_type_str = operation_type.as_str(); let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[self.media_type(), operation_type]) + .with_label_values(&[self.media_type(), operation_type_str]) .start_timer(); - let future = async { + let builder = || async { self.inner .metadata(path) - .verbose_instrument_await("object_store_metadata") + .verbose_instrument_await(operation_type_str) .await }; - let res = match self.read_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) - .await - .unwrap_or_else(|_| Err(ObjectError::internal("metadata timeout"))), - }; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + let res = retry_request( + builder, + &self.config, + operation_type, + self.object_store_metrics.clone(), + ) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); res } pub async fn delete(&self, path: &str) -> ObjectResult<()> { - let operation_type = "delete"; + let operation_type = OperationType::Delete; + let operation_type_str = operation_type.as_str(); let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[self.media_type(), operation_type]) + .with_label_values(&[self.media_type(), operation_type_str]) .start_timer(); - let future = async { + let builder = || async { self.inner .delete(path) - .verbose_instrument_await("object_store_delete") - .await - }; - let res = match self.read_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) + .verbose_instrument_await(operation_type_str) .await - .unwrap_or_else(|_| Err(ObjectError::internal("delete timeout"))), }; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + let res = retry_request( + builder, + &self.config, + operation_type, + self.object_store_metrics.clone(), + ) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); res } async fn delete_objects(&self, paths: &[String]) -> ObjectResult<()> { - let operation_type = "delete_objects"; + let operation_type = OperationType::DeleteObjects; + let operation_type_str = operation_type.as_str(); let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[self.media_type(), operation_type]) + .with_label_values(&[self.media_type(), operation_type_str]) .start_timer(); - let future = async { + let builder = || async { self.inner .delete_objects(paths) - .verbose_instrument_await("object_store_delete_objects") + .verbose_instrument_await(operation_type_str) .await }; - let res = match self.read_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) - .await - .unwrap_or_else(|_| Err(ObjectError::internal("delete_objects timeout"))), - }; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + let res = retry_request( + builder, + &self.config, + operation_type, + self.object_store_metrics.clone(), + ) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); res } pub async fn list(&self, prefix: &str) -> ObjectResult { - let operation_type = "list"; + let operation_type = OperationType::List; + let operation_type_str = operation_type.as_str(); + let _timer = self .object_store_metrics .operation_latency - .with_label_values(&[self.media_type(), operation_type]) + .with_label_values(&[self.media_type(), operation_type_str]) .start_timer(); - let future = async { + let builder = || async { self.inner .list(prefix) - .verbose_instrument_await("object_store_list") + .verbose_instrument_await(operation_type_str) .await }; - let res = match self.read_timeout.as_ref() { - None => future.await, - Some(timeout) => tokio::time::timeout(*timeout, future) - .await - .unwrap_or_else(|_| Err(ObjectError::internal("list timeout"))), - }; - try_update_failure_metric(&self.object_store_metrics, &res, operation_type); + let res = retry_request( + builder, + &self.config, + operation_type, + self.object_store_metrics.clone(), + ) + .await; + + try_update_failure_metric(&self.object_store_metrics, &res, operation_type_str); res } } @@ -788,7 +835,7 @@ pub async fn build_remote_object_store( url: &str, metrics: Arc, ident: &str, - config: ObjectStoreConfig, + config: Arc, ) -> ObjectStoreImpl { tracing::debug!(config=?config, "object store {ident}"); match url { @@ -797,9 +844,13 @@ pub async fn build_remote_object_store( let bucket = s3.strip_prefix("s3://").unwrap(); tracing::info!("Using OpenDAL to access s3, bucket is {}", bucket); ObjectStoreImpl::Opendal( - OpendalObjectStore::new_s3_engine(bucket.to_string(), config.clone()) - .unwrap() - .monitored(metrics, config), + OpendalObjectStore::new_s3_engine( + bucket.to_string(), + config.clone(), + metrics.clone(), + ) + .unwrap() + .monitored(metrics, config), ) } else { ObjectStoreImpl::S3( @@ -822,6 +873,7 @@ pub async fn build_remote_object_store( namenode.to_string(), root.to_string(), config.clone(), + metrics.clone(), ) .unwrap() .monitored(metrics, config), @@ -831,18 +883,28 @@ pub async fn build_remote_object_store( let gcs = gcs.strip_prefix("gcs://").unwrap(); let (bucket, root) = gcs.split_once('@').unwrap_or((gcs, "")); ObjectStoreImpl::Opendal( - OpendalObjectStore::new_gcs_engine(bucket.to_string(), root.to_string()) - .unwrap() - .monitored(metrics, config), + OpendalObjectStore::new_gcs_engine( + bucket.to_string(), + root.to_string(), + config.clone(), + metrics.clone(), + ) + .unwrap() + .monitored(metrics, config), ) } obs if obs.starts_with("obs://") => { let obs = obs.strip_prefix("obs://").unwrap(); let (bucket, root) = obs.split_once('@').unwrap_or((obs, "")); ObjectStoreImpl::Opendal( - OpendalObjectStore::new_obs_engine(bucket.to_string(), root.to_string()) - .unwrap() - .monitored(metrics, config), + OpendalObjectStore::new_obs_engine( + bucket.to_string(), + root.to_string(), + config.clone(), + metrics.clone(), + ) + .unwrap() + .monitored(metrics, config), ) } @@ -850,33 +912,48 @@ pub async fn build_remote_object_store( let oss = oss.strip_prefix("oss://").unwrap(); let (bucket, root) = oss.split_once('@').unwrap_or((oss, "")); ObjectStoreImpl::Opendal( - OpendalObjectStore::new_oss_engine(bucket.to_string(), root.to_string()) - .unwrap() - .monitored(metrics, config), + OpendalObjectStore::new_oss_engine( + bucket.to_string(), + root.to_string(), + config.clone(), + metrics.clone(), + ) + .unwrap() + .monitored(metrics, config), ) } webhdfs if webhdfs.starts_with("webhdfs://") => { let webhdfs = webhdfs.strip_prefix("webhdfs://").unwrap(); let (namenode, root) = webhdfs.split_once('@').unwrap_or((webhdfs, "")); ObjectStoreImpl::Opendal( - OpendalObjectStore::new_webhdfs_engine(namenode.to_string(), root.to_string()) - .unwrap() - .monitored(metrics, config), + OpendalObjectStore::new_webhdfs_engine( + namenode.to_string(), + root.to_string(), + config.clone(), + metrics.clone(), + ) + .unwrap() + .monitored(metrics, config), ) } azblob if azblob.starts_with("azblob://") => { let azblob = azblob.strip_prefix("azblob://").unwrap(); let (container_name, root) = azblob.split_once('@').unwrap_or((azblob, "")); ObjectStoreImpl::Opendal( - OpendalObjectStore::new_azblob_engine(container_name.to_string(), root.to_string()) - .unwrap() - .monitored(metrics, config), + OpendalObjectStore::new_azblob_engine( + container_name.to_string(), + root.to_string(), + config.clone(), + metrics.clone(), + ) + .unwrap() + .monitored(metrics, config), ) } fs if fs.starts_with("fs://") => { let fs = fs.strip_prefix("fs://").unwrap(); ObjectStoreImpl::Opendal( - OpendalObjectStore::new_fs_engine(fs.to_string(), config.clone()) + OpendalObjectStore::new_fs_engine(fs.to_string(), config.clone(), metrics.clone()) .unwrap() .monitored(metrics, config), ) @@ -892,13 +969,13 @@ pub async fn build_remote_object_store( if config.s3.developer.use_opendal { tracing::info!("Using OpenDAL to access minio."); ObjectStoreImpl::Opendal( - OpendalObjectStore::with_minio(minio, config.clone()) + OpendalObjectStore::new_minio_engine(minio, config.clone(), metrics.clone()) .unwrap() .monitored(metrics, config), ) } else { ObjectStoreImpl::S3( - S3ObjectStore::with_minio(minio, metrics.clone(), config.clone()) + S3ObjectStore::new_minio_engine(minio, metrics.clone(), config.clone()) .await .monitored(metrics, config), ) @@ -933,5 +1010,164 @@ pub async fn build_remote_object_store( } } +#[inline(always)] +fn get_retry_strategy( + config: &ObjectStoreConfig, + operation_type: OperationType, +) -> impl Iterator { + let attempts = get_retry_attempts_by_type(config, operation_type); + ExponentialBackoff::from_millis(config.retry.req_backoff_interval_ms) + .max_delay(Duration::from_millis(config.retry.req_backoff_max_delay_ms)) + .factor(config.retry.req_backoff_factor) + .take(attempts) + .map(jitter) +} + pub type ObjectMetadataIter = BoxStream<'static, ObjectResult>; pub type ObjectDataStream = BoxStream<'static, ObjectResult>; + +#[derive(Debug, Clone, Copy)] +enum OperationType { + Upload, + StreamingUploadInit, + StreamingUpload, + StreamingUploadFinish, + Read, + StreamingReadInit, + StreamingRead, + Metadata, + Delete, + DeleteObjects, + List, +} + +impl OperationType { + fn as_str(&self) -> &'static str { + match self { + Self::Upload => "upload", + Self::StreamingUploadInit => "streaming_upload_init", + Self::StreamingUpload => "streaming_upload", + Self::StreamingUploadFinish => "streaming_upload_finish", + Self::Read => "read", + Self::StreamingReadInit => "streaming_read_init", + Self::StreamingRead => "streaming_read", + Self::Metadata => "metadata", + Self::Delete => "delete", + Self::DeleteObjects => "delete_objects", + Self::List => "list", + } + } +} + +fn get_retry_attempts_by_type(config: &ObjectStoreConfig, operation_type: OperationType) -> usize { + match operation_type { + OperationType::Upload => config.retry.upload_retry_attempts, + OperationType::StreamingUploadInit + | OperationType::StreamingUpload + | OperationType::StreamingUploadFinish => config.retry.streaming_upload_retry_attempts, + OperationType::Read => config.retry.read_retry_attempts, + OperationType::StreamingReadInit | OperationType::StreamingRead => { + config.retry.streaming_read_retry_attempts + } + OperationType::Metadata => config.retry.metadata_retry_attempts, + OperationType::Delete => config.retry.delete_retry_attempts, + OperationType::DeleteObjects => config.retry.delete_objects_retry_attempts, + OperationType::List => config.retry.list_retry_attempts, + } +} + +fn get_attempt_timeout_by_type(config: &ObjectStoreConfig, operation_type: OperationType) -> u64 { + match operation_type { + OperationType::Upload => config.retry.upload_attempt_timeout_ms, + OperationType::StreamingUploadInit + | OperationType::StreamingUpload + | OperationType::StreamingUploadFinish => config.retry.streaming_upload_attempt_timeout_ms, + OperationType::Read => config.retry.read_attempt_timeout_ms, + OperationType::StreamingReadInit | OperationType::StreamingRead => { + config.retry.streaming_read_attempt_timeout_ms + } + OperationType::Metadata => config.retry.metadata_attempt_timeout_ms, + OperationType::Delete => config.retry.delete_attempt_timeout_ms, + OperationType::DeleteObjects => config.retry.delete_objects_attempt_timeout_ms, + OperationType::List => config.retry.list_attempt_timeout_ms, + } +} + +struct RetryCondition { + operation_type: OperationType, + retry_count: usize, + metrics: Arc, +} + +impl RetryCondition { + fn new(operation_type: OperationType, metrics: Arc) -> Self { + Self { + operation_type, + retry_count: 0, + metrics, + } + } + + #[inline(always)] + fn should_retry_inner(&mut self, err: &ObjectError) -> bool { + let should_retry = err.should_retry(); + if should_retry { + self.retry_count += 1; + } + + should_retry + } +} + +impl tokio_retry::Condition for RetryCondition { + fn should_retry(&mut self, err: &ObjectError) -> bool { + self.should_retry_inner(err) + } +} + +impl Drop for RetryCondition { + fn drop(&mut self) { + if self.retry_count > 0 { + self.metrics + .request_retry_count + .with_label_values(&[self.operation_type.as_str()]) + .inc_by(self.retry_count as _); + } + } +} + +async fn retry_request( + builder: B, + config: &ObjectStoreConfig, + operation_type: OperationType, + object_store_metrics: Arc, +) -> ObjectResult +where + B: Fn() -> F, + F: Future>, +{ + let backoff = get_retry_strategy(config, operation_type); + let timeout_duration = + Duration::from_millis(get_attempt_timeout_by_type(config, operation_type)); + let operation_type_str = operation_type.as_str(); + + let retry_condition = RetryCondition::new(operation_type, object_store_metrics); + + let f = || async { + let future = builder(); + if timeout_duration.is_zero() { + future.await + } else { + tokio::time::timeout(timeout_duration, future) + .await + .unwrap_or_else(|_| { + Err(ObjectError::timeout(format!( + "Retry attempts exhausted for {}. Please modify {}_attempt_timeout_ms (current={:?}) and {}_retry_attempts (current={}) under [storage.object_store.retry] in the config accordingly if needed.", + operation_type_str, operation_type_str, timeout_duration.as_millis(), operation_type_str, get_retry_attempts_by_type(config, operation_type) + ))) + }) + } + }; + + tokio_retry::RetryIf::spawn(backoff, f, retry_condition).await +} diff --git a/src/object_store/src/object/object_metrics.rs b/src/object_store/src/object/object_metrics.rs index aadf5f35cdb3b..893f914f7f4c4 100644 --- a/src/object_store/src/object/object_metrics.rs +++ b/src/object_store/src/object/object_metrics.rs @@ -90,7 +90,7 @@ impl ObjectStoreMetrics { .unwrap(); let request_retry_count = register_int_counter_vec_with_registry!( - "s3_read_request_retry_count", + "object_store_request_retry_count", "The number of retry times of object store request", &["type"], registry diff --git a/src/object_store/src/object/opendal_engine/azblob.rs b/src/object_store/src/object/opendal_engine/azblob.rs index 8ac8caf09fda3..e584e59aafe8b 100644 --- a/src/object_store/src/object/opendal_engine/azblob.rs +++ b/src/object_store/src/object/opendal_engine/azblob.rs @@ -12,17 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -use opendal::layers::{LoggingLayer, RetryLayer}; +use std::sync::Arc; + +use opendal::layers::LoggingLayer; use opendal::services::Azblob; use opendal::Operator; +use risingwave_common::config::ObjectStoreConfig; use super::{EngineType, OpendalObjectStore}; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::ObjectResult; const AZBLOB_ENDPOINT: &str = "AZBLOB_ENDPOINT"; impl OpendalObjectStore { /// create opendal azblob engine. - pub fn new_azblob_engine(container_name: String, root: String) -> ObjectResult { + pub fn new_azblob_engine( + container_name: String, + root: String, + config: Arc, + metrics: Arc, + ) -> ObjectResult { // Create azblob backend builder. let mut builder = Azblob::default(); builder.root(&root); @@ -35,11 +44,12 @@ impl OpendalObjectStore { let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer(RetryLayer::default()) .finish(); Ok(Self { op, engine_type: EngineType::Azblob, + config, + metrics, }) } } diff --git a/src/object_store/src/object/opendal_engine/fs.rs b/src/object_store/src/object/opendal_engine/fs.rs index f2e8211e93664..2edaaa44d6bbe 100644 --- a/src/object_store/src/object/opendal_engine/fs.rs +++ b/src/object_store/src/object/opendal_engine/fs.rs @@ -12,32 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -use opendal::layers::RetryLayer; +use std::sync::Arc; + +use opendal::layers::LoggingLayer; use opendal::services::Fs; use opendal::Operator; use risingwave_common::config::ObjectStoreConfig; use super::{EngineType, OpendalObjectStore}; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::opendal_engine::ATOMIC_WRITE_DIR; use crate::object::ObjectResult; impl OpendalObjectStore { /// create opendal fs engine. - pub fn new_fs_engine(root: String, config: ObjectStoreConfig) -> ObjectResult { + pub fn new_fs_engine( + root: String, + config: Arc, + metrics: Arc, + ) -> ObjectResult { // Create fs backend builder. let mut builder = Fs::default(); builder.root(&root); - if config.object_store_set_atomic_write_dir { + if config.set_atomic_write_dir { let atomic_write_dir = format!("{}/{}", root, ATOMIC_WRITE_DIR); builder.atomic_write_dir(&atomic_write_dir); } let op: Operator = Operator::new(builder)? - .layer(RetryLayer::default()) + .layer(LoggingLayer::default()) .finish(); Ok(Self { op, engine_type: EngineType::Fs, + config, + metrics, }) } } diff --git a/src/object_store/src/object/opendal_engine/gcs.rs b/src/object_store/src/object/opendal_engine/gcs.rs index 0577288005fca..a3876b30ef564 100644 --- a/src/object_store/src/object/opendal_engine/gcs.rs +++ b/src/object_store/src/object/opendal_engine/gcs.rs @@ -12,16 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -use opendal::layers::{LoggingLayer, RetryLayer}; +use std::sync::Arc; + +use opendal::layers::LoggingLayer; use opendal::services::Gcs; use opendal::Operator; +use risingwave_common::config::ObjectStoreConfig; use super::{EngineType, OpendalObjectStore}; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::ObjectResult; impl OpendalObjectStore { /// create opendal gcs engine. - pub fn new_gcs_engine(bucket: String, root: String) -> ObjectResult { + pub fn new_gcs_engine( + bucket: String, + root: String, + config: Arc, + metrics: Arc, + ) -> ObjectResult { // Create gcs backend builder. let mut builder = Gcs::default(); @@ -37,11 +46,12 @@ impl OpendalObjectStore { let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer(RetryLayer::default()) .finish(); Ok(Self { op, engine_type: EngineType::Gcs, + config, + metrics, }) } } diff --git a/src/object_store/src/object/opendal_engine/hdfs.rs b/src/object_store/src/object/opendal_engine/hdfs.rs index c539cb43cb49d..093a9f2d6c658 100644 --- a/src/object_store/src/object/opendal_engine/hdfs.rs +++ b/src/object_store/src/object/opendal_engine/hdfs.rs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use opendal::layers::{LoggingLayer, RetryLayer}; +use std::sync::Arc; + +use opendal::layers::LoggingLayer; use opendal::services::Hdfs; use opendal::Operator; use risingwave_common::config::ObjectStoreConfig; @@ -26,24 +28,33 @@ impl OpendalObjectStore { pub fn new_hdfs_engine( namenode: String, root: String, - config: ObjectStoreConfig, + config: Arc, + metrics: Arc, ) -> ObjectResult { + // Init the jvm explicitly to avoid duplicate JVM creation by hdfs client + use risingwave_jni_core::jvm_runtime::JVM; + let _ = JVM + .get_or_init() + .inspect_err(|e| tracing::error!("Failed to init JVM: {:?}", e)) + .unwrap(); + // Create hdfs backend builder. let mut builder = Hdfs::default(); // Set the name node for hdfs. builder.name_node(&namenode); builder.root(&root); - if config.object_store_set_atomic_write_dir { + if config.set_atomic_write_dir { let atomic_write_dir = format!("{}/{}", root, ATOMIC_WRITE_DIR); builder.atomic_write_dir(&atomic_write_dir); } let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer(RetryLayer::default()) .finish(); Ok(Self { op, engine_type: EngineType::Hdfs, + config, + metrics, }) } } diff --git a/src/object_store/src/object/opendal_engine/obs.rs b/src/object_store/src/object/opendal_engine/obs.rs index 4ddf9579685f1..03919ec57d37c 100644 --- a/src/object_store/src/object/opendal_engine/obs.rs +++ b/src/object_store/src/object/opendal_engine/obs.rs @@ -12,16 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -use opendal::layers::{LoggingLayer, RetryLayer}; +use std::sync::Arc; + +use opendal::layers::LoggingLayer; use opendal::services::Obs; use opendal::Operator; +use risingwave_common::config::ObjectStoreConfig; use super::{EngineType, OpendalObjectStore}; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::ObjectResult; impl OpendalObjectStore { /// create opendal obs engine. - pub fn new_obs_engine(bucket: String, root: String) -> ObjectResult { + pub fn new_obs_engine( + bucket: String, + root: String, + config: Arc, + metrics: Arc, + ) -> ObjectResult { // Create obs backend builder. let mut builder = Obs::default(); @@ -43,11 +52,12 @@ impl OpendalObjectStore { let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer(RetryLayer::default()) .finish(); Ok(Self { op, engine_type: EngineType::Obs, + config, + metrics, }) } } diff --git a/src/object_store/src/object/opendal_engine/opendal_object_store.rs b/src/object_store/src/object/opendal_engine/opendal_object_store.rs index d7ba829dda632..6ea0cbb6fe8f0 100644 --- a/src/object_store/src/object/opendal_engine/opendal_object_store.rs +++ b/src/object_store/src/object/opendal_engine/opendal_object_store.rs @@ -13,18 +13,24 @@ // limitations under the License. use std::ops::Range; +use std::sync::Arc; +use std::time::Duration; use bytes::Bytes; use fail::fail_point; -use futures::{stream, StreamExt, TryStreamExt}; +use futures::{stream, StreamExt}; +use opendal::layers::{RetryLayer, TimeoutLayer}; +use opendal::raw::BoxedStaticFuture; use opendal::services::Memory; -use opendal::{Metakey, Operator, Writer}; +use opendal::{Execute, Executor, Metakey, Operator, Writer}; +use risingwave_common::config::ObjectStoreConfig; use risingwave_common::range::RangeBoundsExt; use thiserror_ext::AsReport; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::{ - prefix, BoxedStreamingUploader, ObjectDataStream, ObjectError, ObjectMetadata, - ObjectMetadataIter, ObjectRangeBounds, ObjectResult, ObjectStore, StreamingUploader, + prefix, ObjectDataStream, ObjectError, ObjectMetadata, ObjectMetadataIter, ObjectRangeBounds, + ObjectResult, ObjectStore, OperationType, StreamingUploader, }; /// Opendal object storage. @@ -32,6 +38,9 @@ use crate::object::{ pub struct OpendalObjectStore { pub(crate) op: Operator, pub(crate) engine_type: EngineType, + + pub(crate) config: Arc, + pub(crate) metrics: Arc, } #[derive(Clone)] @@ -50,31 +59,37 @@ pub enum EngineType { impl OpendalObjectStore { /// create opendal memory engine, used for unit tests. - pub fn new_memory_engine() -> ObjectResult { + pub fn test_new_memory_engine() -> ObjectResult { // Create memory backend builder. let builder = Memory::default(); let op: Operator = Operator::new(builder)?.finish(); Ok(Self { op, engine_type: EngineType::Memory, + config: Arc::new(ObjectStoreConfig::default()), + metrics: Arc::new(ObjectStoreMetrics::unused()), }) } } #[async_trait::async_trait] impl ObjectStore for OpendalObjectStore { - fn get_object_prefix(&self, obj_id: u64) -> String { + type StreamingUploader = OpendalStreamingUploader; + + fn get_object_prefix(&self, obj_id: u64, use_new_object_prefix_strategy: bool) -> String { match self.engine_type { EngineType::S3 => prefix::s3::get_object_prefix(obj_id), EngineType::Minio => prefix::s3::get_object_prefix(obj_id), EngineType::Memory => String::default(), - EngineType::Hdfs => String::default(), - EngineType::Gcs => String::default(), - EngineType::Obs => String::default(), - EngineType::Oss => String::default(), - EngineType::Webhdfs => String::default(), - EngineType::Azblob => String::default(), - EngineType::Fs => String::default(), + EngineType::Hdfs + | EngineType::Gcs + | EngineType::Obs + | EngineType::Oss + | EngineType::Webhdfs + | EngineType::Azblob + | EngineType::Fs => { + prefix::opendal_engine::get_object_prefix(obj_id, use_new_object_prefix_strategy) + } } } @@ -87,10 +102,15 @@ impl ObjectStore for OpendalObjectStore { } } - async fn streaming_upload(&self, path: &str) -> ObjectResult { - Ok(Box::new( - OpendalStreamingUploader::new(self.op.clone(), path.to_string()).await?, - )) + async fn streaming_upload(&self, path: &str) -> ObjectResult { + Ok(OpendalStreamingUploader::new( + self.op.clone(), + path.to_string(), + self.config.clone(), + self.metrics.clone(), + self.store_media_type(), + ) + .await?) } async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult { @@ -115,7 +135,7 @@ impl ObjectStore for OpendalObjectStore { ))); } - Ok(Bytes::from(data)) + Ok(data.to_bytes()) } /// Returns a stream reading the object specified in `path`. If given, the stream starts at the @@ -130,9 +150,36 @@ impl ObjectStore for OpendalObjectStore { ObjectError::internal("opendal streaming read error") )); let range: Range = (range.start as u64)..(range.end as u64); - let reader = self.op.reader_with(path).range(range).await?; - let stream = reader.into_stream().map(|item| { - item.map_err(|e| ObjectError::internal(format!("OpendalError: {}", e.as_report()))) + + // The layer specified first will be executed first. + // `TimeoutLayer` must be specified before `RetryLayer`. + // Otherwise, it will lead to bad state inside OpenDAL and panic. + // See https://docs.rs/opendal/latest/opendal/layers/struct.RetryLayer.html#panics + let reader = self + .op + .clone() + .layer(TimeoutLayer::new().with_io_timeout(Duration::from_millis( + self.config.retry.streaming_read_attempt_timeout_ms, + ))) + .layer( + RetryLayer::new() + .with_min_delay(Duration::from_millis( + self.config.retry.req_backoff_interval_ms, + )) + .with_max_delay(Duration::from_millis( + self.config.retry.req_backoff_max_delay_ms, + )) + .with_max_times(self.config.retry.streaming_read_retry_attempts) + .with_factor(self.config.retry.req_backoff_factor as f32) + .with_jitter(), + ) + .reader_with(path) + .await?; + let stream = reader.into_bytes_stream(range).await?.map(|item| { + item.map(|b| Bytes::copy_from_slice(b.as_ref())) + .map_err(|e| { + ObjectError::internal(format!("reader into_stream fail {}", e.as_report())) + }) }); Ok(Box::pin(stream)) @@ -214,38 +261,147 @@ impl ObjectStore for OpendalObjectStore { EngineType::Fs => "Fs", } } + + fn support_streaming_upload(&self) -> bool { + self.op.info().native_capability().write_can_multi + } +} + +struct OpendalStreamingUploaderExecute { + /// To record metrics for uploading part. + metrics: Arc, + media_type: &'static str, +} + +impl OpendalStreamingUploaderExecute { + const STREAMING_UPLOAD_TYPE: OperationType = OperationType::StreamingUpload; + + fn new(metrics: Arc, media_type: &'static str) -> Self { + Self { + metrics, + media_type, + } + } +} + +impl Execute for OpendalStreamingUploaderExecute { + fn execute(&self, f: BoxedStaticFuture<()>) { + let operation_type_str = Self::STREAMING_UPLOAD_TYPE.as_str(); + let media_type = self.media_type; + + let metrics = self.metrics.clone(); + let _handle = tokio::spawn(async move { + let _timer = metrics + .operation_latency + .with_label_values(&[media_type, operation_type_str]) + .start_timer(); + + f.await + }); + } } /// Store multiple parts in a map, and concatenate them on finish. pub struct OpendalStreamingUploader { writer: Writer, + /// Buffer for data. It will store at least `UPLOAD_BUFFER_SIZE` bytes of data before wrapping itself + /// into a stream and upload to object store as a part. + buf: Vec, + /// Length of the data that have not been uploaded to object store. + not_uploaded_len: usize, + /// Whether the writer is valid. The writer is invalid after abort/close. + is_valid: bool, + + abort_on_err: bool, } impl OpendalStreamingUploader { - pub async fn new(op: Operator, path: String) -> ObjectResult { + const UPLOAD_BUFFER_SIZE: usize = 16 * 1024 * 1024; + + pub async fn new( + op: Operator, + path: String, + config: Arc, + metrics: Arc, + media_type: &'static str, + ) -> ObjectResult { + let monitored_execute = OpendalStreamingUploaderExecute::new(metrics, media_type); + + // The layer specified first will be executed first. + // `TimeoutLayer` must be specified before `RetryLayer`. + // Otherwise, it will lead to bad state inside OpenDAL and panic. + // See https://docs.rs/opendal/latest/opendal/layers/struct.RetryLayer.html#panics let writer = op + .clone() + .layer(TimeoutLayer::new().with_io_timeout(Duration::from_millis( + config.retry.streaming_upload_attempt_timeout_ms, + ))) + .layer( + RetryLayer::new() + .with_min_delay(Duration::from_millis(config.retry.req_backoff_interval_ms)) + .with_max_delay(Duration::from_millis(config.retry.req_backoff_max_delay_ms)) + .with_max_times(config.retry.streaming_upload_retry_attempts) + .with_factor(config.retry.req_backoff_factor as f32) + .with_jitter(), + ) .writer_with(&path) - .concurrent(8) - .buffer(OPENDAL_BUFFER_SIZE) + .concurrent(config.opendal_upload_concurrency) + .executor(Executor::with(monitored_execute)) .await?; - Ok(Self { writer }) + Ok(Self { + writer, + buf: vec![], + not_uploaded_len: 0, + is_valid: true, + abort_on_err: config.opendal_writer_abort_on_err, + }) } -} -const OPENDAL_BUFFER_SIZE: usize = 16 * 1024 * 1024; + async fn flush(&mut self) -> ObjectResult<()> { + let data: Vec = self.buf.drain(..).collect(); + debug_assert_eq!( + data.iter().map(|b| b.len()).sum::(), + self.not_uploaded_len + ); + if let Err(err) = self.writer.write(data).await { + self.is_valid = false; + if self.abort_on_err { + self.writer.abort().await?; + } + return Err(err.into()); + } + self.not_uploaded_len = 0; + Ok(()) + } +} -#[async_trait::async_trait] impl StreamingUploader for OpendalStreamingUploader { async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> { - self.writer.write(data).await?; + assert!(self.is_valid); + self.not_uploaded_len += data.len(); + self.buf.push(data); + if self.not_uploaded_len >= Self::UPLOAD_BUFFER_SIZE { + self.flush().await?; + } Ok(()) } - async fn finish(mut self: Box) -> ObjectResult<()> { + async fn finish(mut self) -> ObjectResult<()> { + assert!(self.is_valid); + if self.not_uploaded_len > 0 { + self.flush().await?; + } + + assert!(self.buf.is_empty()); + assert_eq!(self.not_uploaded_len, 0); + + self.is_valid = false; match self.writer.close().await { Ok(_) => (), Err(err) => { - self.writer.abort().await?; + if self.abort_on_err { + self.writer.abort().await?; + } return Err(err.into()); } }; @@ -254,13 +410,13 @@ impl StreamingUploader for OpendalStreamingUploader { } fn get_memory_usage(&self) -> u64 { - OPENDAL_BUFFER_SIZE as u64 + Self::UPLOAD_BUFFER_SIZE as u64 } } #[cfg(test)] mod tests { - use bytes::Bytes; + use stream::TryStreamExt; use super::*; @@ -277,7 +433,7 @@ mod tests { #[tokio::test] async fn test_memory_upload() { let block = Bytes::from("123456"); - let store = OpendalObjectStore::new_memory_engine().unwrap(); + let store = OpendalObjectStore::test_new_memory_engine().unwrap(); store.upload("/abc", block).await.unwrap(); // No such object. @@ -286,20 +442,28 @@ mod tests { let bytes = store.read("/abc", 4..6).await.unwrap(); assert_eq!(String::from_utf8(bytes.to_vec()).unwrap(), "56".to_string()); - // Overflow. - store.read("/abc", 4..44).await.unwrap_err(); - store.delete("/abc").await.unwrap(); // No such object. store.read("/abc", 0..3).await.unwrap_err(); } + #[tokio::test] + #[should_panic] + async fn test_memory_read_overflow() { + let block = Bytes::from("123456"); + let store = OpendalObjectStore::test_new_memory_engine().unwrap(); + store.upload("/abc", block).await.unwrap(); + + // Overflow. + store.read("/abc", 4..44).await.unwrap_err(); + } + #[tokio::test] async fn test_memory_metadata() { let block = Bytes::from("123456"); let path = "/abc".to_string(); - let obj_store = OpendalObjectStore::new_memory_engine().unwrap(); + let obj_store = OpendalObjectStore::test_new_memory_engine().unwrap(); obj_store.upload("/abc", block).await.unwrap(); let err = obj_store.metadata("/not_exist").await.unwrap_err(); @@ -313,7 +477,7 @@ mod tests { async fn test_memory_delete_objects_and_list_object() { let block1 = Bytes::from("123456"); let block2 = Bytes::from("987654"); - let store = OpendalObjectStore::new_memory_engine().unwrap(); + let store = OpendalObjectStore::test_new_memory_engine().unwrap(); store.upload("abc", Bytes::from("123456")).await.unwrap(); store.upload("prefix/abc", block1).await.unwrap(); diff --git a/src/object_store/src/object/opendal_engine/opendal_s3.rs b/src/object_store/src/object/opendal_engine/opendal_s3.rs index c83498305d8e5..5ba90ad93ccba 100644 --- a/src/object_store/src/object/opendal_engine/opendal_s3.rs +++ b/src/object_store/src/object/opendal_engine/opendal_s3.rs @@ -12,22 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; use std::time::Duration; -use opendal::layers::{LoggingLayer, RetryLayer}; +use opendal::layers::LoggingLayer; use opendal::raw::HttpClient; use opendal::services::S3; use opendal::Operator; use risingwave_common::config::ObjectStoreConfig; use super::{EngineType, OpendalObjectStore}; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::ObjectResult; impl OpendalObjectStore { /// create opendal s3 engine. pub fn new_s3_engine( bucket: String, - object_store_config: ObjectStoreConfig, + config: Arc, + metrics: Arc, ) -> ObjectResult { // Create s3 builder. let mut builder = S3::default(); @@ -41,33 +44,27 @@ impl OpendalObjectStore { builder.enable_virtual_host_style(); } - let http_client = Self::new_http_client(&object_store_config)?; + let http_client = Self::new_http_client(&config)?; builder.http_client(http_client); let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer( - RetryLayer::new() - .with_min_delay(Duration::from_millis( - object_store_config.s3.object_store_req_retry_interval_ms, - )) - .with_max_delay(Duration::from_millis( - object_store_config.s3.object_store_req_retry_max_delay_ms, - )) - .with_max_times(object_store_config.s3.object_store_req_retry_max_attempts) - .with_factor(1.0) - .with_jitter(), - ) .finish(); Ok(Self { op, engine_type: EngineType::S3, + config, + metrics, }) } /// Creates a minio client. The server should be like `minio://key:secret@address:port/bucket`. - pub fn with_minio(server: &str, object_store_config: ObjectStoreConfig) -> ObjectResult { + pub fn new_minio_engine( + server: &str, + config: Arc, + metrics: Arc, + ) -> ObjectResult { let server = server.strip_prefix("minio://").unwrap(); let (access_key_id, rest) = server.split_once(':').unwrap(); let (secret_access_key, mut rest) = rest.split_once('@').unwrap(); @@ -92,38 +89,28 @@ impl OpendalObjectStore { .endpoint(&format!("{}{}", endpoint_prefix, address)); builder.disable_config_load(); - let http_client = Self::new_http_client(&object_store_config)?; + let http_client = Self::new_http_client(&config)?; builder.http_client(http_client); let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer( - RetryLayer::new() - .with_min_delay(Duration::from_millis( - object_store_config.s3.object_store_req_retry_interval_ms, - )) - .with_max_delay(Duration::from_millis( - object_store_config.s3.object_store_req_retry_max_delay_ms, - )) - .with_max_times(object_store_config.s3.object_store_req_retry_max_attempts) - .with_factor(1.0) - .with_jitter(), - ) .finish(); Ok(Self { op, engine_type: EngineType::Minio, + config, + metrics, }) } pub fn new_http_client(config: &ObjectStoreConfig) -> ObjectResult { let mut client_builder = reqwest::ClientBuilder::new(); - if let Some(keepalive_ms) = config.s3.object_store_keepalive_ms.as_ref() { + if let Some(keepalive_ms) = config.s3.keepalive_ms.as_ref() { client_builder = client_builder.tcp_keepalive(Duration::from_millis(*keepalive_ms)); } - if let Some(nodelay) = config.s3.object_store_nodelay.as_ref() { + if let Some(nodelay) = config.s3.nodelay.as_ref() { client_builder = client_builder.tcp_nodelay(*nodelay); } @@ -134,7 +121,8 @@ impl OpendalObjectStore { /// especially when sinking to the intermediate s3 bucket. pub fn new_s3_engine_with_credentials( bucket: &str, - object_store_config: ObjectStoreConfig, + config: Arc, + metrics: Arc, aws_access_key_id: &str, aws_secret_access_key: &str, aws_region: &str, @@ -148,28 +136,18 @@ impl OpendalObjectStore { builder.secret_access_key(aws_secret_access_key); builder.region(aws_region); - let http_client = Self::new_http_client(&object_store_config)?; + let http_client = Self::new_http_client(config.as_ref())?; builder.http_client(http_client); let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer( - RetryLayer::new() - .with_min_delay(Duration::from_millis( - object_store_config.s3.object_store_req_retry_interval_ms, - )) - .with_max_delay(Duration::from_millis( - object_store_config.s3.object_store_req_retry_max_delay_ms, - )) - .with_max_times(object_store_config.s3.object_store_req_retry_max_attempts) - .with_factor(1.1) - .with_jitter(), - ) .finish(); Ok(Self { op, engine_type: EngineType::S3, + config, + metrics, }) } } diff --git a/src/object_store/src/object/opendal_engine/oss.rs b/src/object_store/src/object/opendal_engine/oss.rs index e215b6f93d31e..c4fc5d500b11e 100644 --- a/src/object_store/src/object/opendal_engine/oss.rs +++ b/src/object_store/src/object/opendal_engine/oss.rs @@ -12,16 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -use opendal::layers::{LoggingLayer, RetryLayer}; +use std::sync::Arc; + +use opendal::layers::LoggingLayer; use opendal::services::Oss; use opendal::Operator; +use risingwave_common::config::ObjectStoreConfig; use super::{EngineType, OpendalObjectStore}; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::ObjectResult; impl OpendalObjectStore { /// create opendal oss engine. - pub fn new_oss_engine(bucket: String, root: String) -> ObjectResult { + pub fn new_oss_engine( + bucket: String, + root: String, + config: Arc, + metrics: Arc, + ) -> ObjectResult { // Create oss backend builder. let mut builder = Oss::default(); @@ -43,11 +52,12 @@ impl OpendalObjectStore { let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer(RetryLayer::default()) .finish(); Ok(Self { op, engine_type: EngineType::Oss, + config, + metrics, }) } } diff --git a/src/object_store/src/object/opendal_engine/webhdfs.rs b/src/object_store/src/object/opendal_engine/webhdfs.rs index 1f6b87b44fd5e..f083102a3ed21 100644 --- a/src/object_store/src/object/opendal_engine/webhdfs.rs +++ b/src/object_store/src/object/opendal_engine/webhdfs.rs @@ -12,17 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -use opendal::layers::{LoggingLayer, RetryLayer}; +use std::sync::Arc; + +use opendal::layers::LoggingLayer; use opendal::services::Webhdfs; use opendal::Operator; +use risingwave_common::config::ObjectStoreConfig; use super::{EngineType, OpendalObjectStore}; +use crate::object::object_metrics::ObjectStoreMetrics; use crate::object::opendal_engine::ATOMIC_WRITE_DIR; use crate::object::ObjectResult; impl OpendalObjectStore { /// create opendal webhdfs engine. - pub fn new_webhdfs_engine(endpoint: String, root: String) -> ObjectResult { + pub fn new_webhdfs_engine( + endpoint: String, + root: String, + config: Arc, + metrics: Arc, + ) -> ObjectResult { // Create webhdfs backend builder. let mut builder = Webhdfs::default(); // Set the name node for webhdfs. @@ -35,11 +44,12 @@ impl OpendalObjectStore { builder.atomic_write_dir(&atomic_write_dir); let op: Operator = Operator::new(builder)? .layer(LoggingLayer::default()) - .layer(RetryLayer::default()) .finish(); Ok(Self { op, engine_type: EngineType::Webhdfs, + config, + metrics, }) } } diff --git a/src/object_store/src/object/prefix.rs b/src/object_store/src/object/prefix.rs index e29729bf752fc..5229d900a8b62 100644 --- a/src/object_store/src/object/prefix.rs +++ b/src/object_store/src/object/prefix.rs @@ -23,3 +23,23 @@ pub(crate) mod s3 { obj_prefix } } + +pub(crate) mod opendal_engine { + /// The number of Azblob bucket prefixes + pub(crate) const NUM_BUCKET_PREFIXES_AZBLOB: u32 = 256; + + pub(crate) fn get_object_prefix(obj_id: u64, use_new_object_prefix_strategy: bool) -> String { + // For OpenDAL object storage, whether objects are divided by prefixes depends on whether it is a new cluster: + // If it is a new cluster, objects will be divided into `NUM_BUCKET_PREFIXES_AZBLOB` prefixes. + // If it is an old cluster, prefixes are not used due to the need to read and write old data. + match use_new_object_prefix_strategy { + true => { + let prefix = crc32fast::hash(&obj_id.to_be_bytes()) % NUM_BUCKET_PREFIXES_AZBLOB; + let mut obj_prefix = prefix.to_string(); + obj_prefix.push('/'); + obj_prefix + } + false => String::default(), + } + } +} diff --git a/src/object_store/src/object/s3.rs b/src/object_store/src/object/s3.rs index 9d48f1175976b..3ed5fc01ba40c 100644 --- a/src/object_store/src/object/s3.rs +++ b/src/object_store/src/object/s3.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::borrow::BorrowMut; use std::cmp; use std::collections::VecDeque; use std::ops::Range; @@ -20,12 +21,21 @@ use std::sync::Arc; use std::task::{ready, Context, Poll}; use std::time::Duration; +use await_tree::InstrumentAwait; use aws_sdk_s3::config::{Credentials, Region}; +use aws_sdk_s3::error::BoxError; +use aws_sdk_s3::operation::abort_multipart_upload::AbortMultipartUploadError; +use aws_sdk_s3::operation::complete_multipart_upload::CompleteMultipartUploadError; +use aws_sdk_s3::operation::create_multipart_upload::CreateMultipartUploadError; +use aws_sdk_s3::operation::delete_object::DeleteObjectError; +use aws_sdk_s3::operation::delete_objects::DeleteObjectsError; use aws_sdk_s3::operation::get_object::builders::GetObjectFluentBuilder; use aws_sdk_s3::operation::get_object::GetObjectError; +use aws_sdk_s3::operation::head_object::HeadObjectError; use aws_sdk_s3::operation::list_objects_v2::ListObjectsV2Error; +use aws_sdk_s3::operation::put_object::PutObjectError; use aws_sdk_s3::operation::upload_part::UploadPartOutput; -use aws_sdk_s3::primitives::{ByteStream, ByteStreamError}; +use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::types::{ AbortIncompleteMultipartUpload, BucketLifecycleConfiguration, CompletedMultipartUpload, CompletedPart, Delete, ExpirationStatus, LifecycleRule, LifecycleRuleFilter, ObjectIdentifier, @@ -37,26 +47,25 @@ use aws_smithy_runtime_api::client::http::HttpClient; use aws_smithy_runtime_api::client::result::SdkError; use aws_smithy_types::body::SdkBody; use aws_smithy_types::error::metadata::ProvideErrorMetadata; -use aws_smithy_types::retry::RetryConfig; -use either::Either; use fail::fail_point; use futures::future::{try_join_all, BoxFuture, FutureExt}; use futures::{stream, Stream, StreamExt, TryStreamExt}; use hyper::Body; use itertools::Itertools; -use risingwave_common::config::{ObjectStoreConfig, S3ObjectStoreConfig}; +use risingwave_common::config::ObjectStoreConfig; use risingwave_common::monitor::connection::monitor_connector; use risingwave_common::range::RangeBoundsExt; use thiserror_ext::AsReport; use tokio::task::JoinHandle; -use tokio_retry::strategy::{jitter, ExponentialBackoff}; use super::object_metrics::ObjectStoreMetrics; use super::{ - prefix, BoxedStreamingUploader, Bytes, ObjectError, ObjectMetadata, ObjectRangeBounds, + prefix, retry_request, Bytes, ObjectError, ObjectErrorInner, ObjectMetadata, ObjectRangeBounds, ObjectResult, ObjectStore, StreamingUploader, }; -use crate::object::{try_update_failure_metric, ObjectDataStream, ObjectMetadataIter}; +use crate::object::{ + try_update_failure_metric, ObjectDataStream, ObjectMetadataIter, OperationType, +}; type PartId = i32; @@ -96,6 +105,8 @@ pub struct S3StreamingUploader { not_uploaded_len: usize, /// To record metrics for uploading part. metrics: Arc, + + config: Arc, } impl S3StreamingUploader { @@ -105,6 +116,7 @@ impl S3StreamingUploader { part_size: usize, key: String, metrics: Arc, + config: Arc, ) -> S3StreamingUploader { Self { client, @@ -117,22 +129,46 @@ impl S3StreamingUploader { buf: Default::default(), not_uploaded_len: 0, metrics, + config, } } async fn upload_next_part(&mut self) -> ObjectResult<()> { - let operation_type = "s3_upload_part"; + let operation_type = OperationType::StreamingUpload; + let operation_type_str = operation_type.as_str(); // Lazily create multipart upload. if self.upload_id.is_none() { - let resp = self - .client - .create_multipart_upload() - .bucket(&self.bucket) - .key(&self.key) - .send() - .await?; - self.upload_id = Some(resp.upload_id.unwrap()); + let builder = || async { + self.client + .create_multipart_upload() + .bucket(&self.bucket) + .key(&self.key) + .send() + .await + .map_err(|err| { + set_error_should_retry::( + self.config.clone(), + err.into(), + ) + }) + }; + + let resp = retry_request( + builder, + &self.config, + OperationType::StreamingUploadInit, + self.metrics.clone(), + ) + .await; + + try_update_failure_metric( + &self.metrics, + &resp, + OperationType::StreamingUploadInit.as_str(), + ); + + self.upload_id = Some(resp?.upload_id.unwrap()); } // Get the data to upload for the next part. @@ -156,33 +192,46 @@ impl S3StreamingUploader { let metrics = self.metrics.clone(); metrics .operation_size - .with_label_values(&[operation_type]) + .with_label_values(&[operation_type_str]) .observe(len as f64); + let config = self.config.clone(); self.join_handles.push(tokio::spawn(async move { let _timer = metrics .operation_latency - .with_label_values(&["s3", operation_type]) + .with_label_values(&["s3", operation_type_str]) .start_timer(); - let upload_output_res = client_cloned - .upload_part() - .bucket(bucket) - .key(key) - .upload_id(upload_id) - .part_number(part_id) - .body(get_upload_body(data)) - .content_length(len as i64) - .send() - .await - .map_err(Into::into); - try_update_failure_metric(&metrics, &upload_output_res, operation_type); - Ok((part_id, upload_output_res?)) + + let builder = || async { + client_cloned + .upload_part() + .bucket(bucket.clone()) + .key(key.clone()) + .upload_id(upload_id.clone()) + .part_number(part_id) + .body(get_upload_body(data.clone())) + .content_length(len as i64) + .send() + .await + .map_err(|err| { + set_error_should_retry::( + config.clone(), + err.into(), + ) + }) + }; + + let res = retry_request(builder, &config, operation_type, metrics.clone()).await; + try_update_failure_metric(&metrics, &res, operation_type_str); + Ok((part_id, res?)) })); Ok(()) } async fn flush_multipart_and_complete(&mut self) -> ObjectResult<()> { + let operation_type = OperationType::StreamingUploadFinish; + if !self.buf.is_empty() { self.upload_next_part().await?; } @@ -210,18 +259,30 @@ impl S3StreamingUploader { .collect_vec(), ); - self.client - .complete_multipart_upload() - .bucket(&self.bucket) - .key(&self.key) - .upload_id(self.upload_id.as_ref().unwrap()) - .multipart_upload( - CompletedMultipartUpload::builder() - .set_parts(completed_parts) - .build(), - ) - .send() - .await?; + let builder = || async { + self.client + .complete_multipart_upload() + .bucket(&self.bucket) + .key(&self.key) + .upload_id(self.upload_id.as_ref().unwrap()) + .multipart_upload( + CompletedMultipartUpload::builder() + .set_parts(completed_parts.clone()) + .build(), + ) + .send() + .await + .map_err(|err| { + set_error_should_retry::( + self.config.clone(), + err.into(), + ) + }) + }; + + let res = retry_request(builder, &self.config, operation_type, self.metrics.clone()).await; + try_update_failure_metric(&self.metrics, &res, operation_type.as_str()); + let _res = res?; Ok(()) } @@ -233,12 +294,14 @@ impl S3StreamingUploader { .key(&self.key) .upload_id(self.upload_id.as_ref().unwrap()) .send() - .await?; + .await + .map_err(|err| { + set_error_should_retry::(self.config.clone(), err.into()) + })?; Ok(()) } } -#[async_trait::async_trait] impl StreamingUploader for S3StreamingUploader { async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> { fail_point!("s3_write_bytes_err", |_| Err(ObjectError::internal( @@ -249,7 +312,9 @@ impl StreamingUploader for S3StreamingUploader { self.buf.push(data); if self.not_uploaded_len >= self.part_size { - self.upload_next_part().await?; + self.upload_next_part() + .verbose_instrument_await("s3_upload_next_part") + .await?; self.not_uploaded_len = 0; } Ok(()) @@ -258,7 +323,7 @@ impl StreamingUploader for S3StreamingUploader { /// If the multipart upload has not been initiated, we can use `PutObject` instead to save the /// `CreateMultipartUpload` and `CompleteMultipartUpload` requests. Otherwise flush the /// remaining data of the buffer to S3 as a new part. - async fn finish(mut self: Box) -> ObjectResult<()> { + async fn finish(mut self) -> ObjectResult<()> { fail_point!("s3_finish_streaming_upload_err", |_| Err( ObjectError::internal("s3 finish streaming upload error") )); @@ -269,17 +334,37 @@ impl StreamingUploader for S3StreamingUploader { debug_assert_eq!(self.not_uploaded_len, 0); Err(ObjectError::internal("upload empty object")) } else { - self.client - .put_object() - .bucket(&self.bucket) - .body(get_upload_body(self.buf)) - .content_length(self.not_uploaded_len as i64) - .key(&self.key) - .send() - .await?; + let operation_type = OperationType::Upload; + let builder = || async { + self.client + .put_object() + .bucket(&self.bucket) + .body(get_upload_body(self.buf.clone())) + .content_length(self.not_uploaded_len as i64) + .key(&self.key) + .send() + .verbose_instrument_await("s3_put_object") + .await + .map_err(|err| { + set_error_should_retry::( + self.config.clone(), + err.into(), + ) + }) + }; + + let res = + retry_request(builder, &self.config, operation_type, self.metrics.clone()) + .await; + try_update_failure_metric(&self.metrics, &res, operation_type.as_str()); + res?; Ok(()) } - } else if let Err(e) = self.flush_multipart_and_complete().await { + } else if let Err(e) = self + .flush_multipart_and_complete() + .verbose_instrument_await("s3_flush_multipart_and_complete") + .await + { tracing::warn!(key = self.key, error = %e.as_report(), "Failed to upload object"); self.abort_multipart_upload().await?; Err(e) @@ -302,6 +387,7 @@ fn get_upload_body(data: Vec) -> ByteStream { /// Object store with S3 backend /// The full path to a file on S3 would be `s3://bucket//prefix/file` +#[derive(Clone)] pub struct S3ObjectStore { client: Client, bucket: String, @@ -309,13 +395,16 @@ pub struct S3ObjectStore { /// For S3 specific metrics. metrics: Arc, - config: ObjectStoreConfig, + config: Arc, } #[async_trait::async_trait] impl ObjectStore for S3ObjectStore { - fn get_object_prefix(&self, obj_id: u64) -> String { + type StreamingUploader = S3StreamingUploader; + + fn get_object_prefix(&self, obj_id: u64, _use_new_object_prefix_strategy: bool) -> String { // Delegate to static method to avoid creating an `S3ObjectStore` in unit test. + // Using aws s3 sdk as object storage, the object prefix will be divided by default. prefix::s3::get_object_prefix(obj_id) } @@ -332,22 +421,26 @@ impl ObjectStore for S3ObjectStore { .body(ByteStream::from(obj)) .key(path) .send() - .await?; + .await + .map_err(|err| { + set_error_should_retry::(self.config.clone(), err.into()) + })?; Ok(()) } } - async fn streaming_upload(&self, path: &str) -> ObjectResult { + async fn streaming_upload(&self, path: &str) -> ObjectResult { fail_point!("s3_streaming_upload_err", |_| Err(ObjectError::internal( "s3 streaming upload error" ))); - Ok(Box::new(S3StreamingUploader::new( + Ok(S3StreamingUploader::new( self.client.clone(), self.bucket.clone(), self.part_size, path.to_string(), self.metrics.clone(), - ))) + self.config.clone(), + )) } /// Amazon S3 doesn't support retrieving multiple ranges of data per GET request. @@ -356,37 +449,22 @@ impl ObjectStore for S3ObjectStore { "s3 read error" ))); - // retry if occurs AWS EC2 HTTP timeout error. - let val = tokio_retry::RetryIf::spawn( - self.get_retry_strategy(), - || async { - match self.obj_store_request(path, range.clone()).send().await { - Ok(resp) => { - let val = resp - .body - .collect() - .await - .map_err(either::Right)? - .into_bytes(); - Ok(val) - } - Err(err) => { - if let SdkError::DispatchFailure(e) = &err - && e.is_timeout() - { - self.metrics - .request_retry_count - .with_label_values(&["read"]) - .inc(); - } - - Err(either::Left(err)) - } - } - }, - RetryCondition::new(&self.config.s3), - ) - .await?; + let val = match self.obj_store_request(path, range.clone()).send().await { + Ok(resp) => resp + .body + .collect() + .await + .map_err(|err| { + set_error_should_retry::(self.config.clone(), err.into()) + })? + .into_bytes(), + Err(sdk_err) => { + return Err(set_error_should_retry::( + self.config.clone(), + sdk_err.into(), + )); + } + }; if let Some(len) = range.len() && len != val.len() @@ -413,7 +491,10 @@ impl ObjectStore for S3ObjectStore { .bucket(&self.bucket) .key(path) .send() - .await?; + .await + .map_err(|err| { + set_error_should_retry::(self.config.clone(), err.into()) + })?; Ok(ObjectMetadata { key: path.to_owned(), last_modified: resp @@ -432,33 +513,20 @@ impl ObjectStore for S3ObjectStore { path: &str, range: Range, ) -> ObjectResult { - fail_point!("s3_streaming_read_err", |_| Err(ObjectError::internal( - "s3 streaming read error" - ))); + fail_point!("s3_streaming_read_init_err", |_| Err( + ObjectError::internal("s3 streaming read init error") + )); - // retry if occurs AWS EC2 HTTP timeout error. - let resp = tokio_retry::RetryIf::spawn( - self.get_retry_strategy(), - || async { - match self.obj_store_request(path, range.clone()).send().await { - Ok(resp) => Ok(resp), - Err(err) => { - if let SdkError::DispatchFailure(e) = &err - && e.is_timeout() - { - self.metrics - .request_retry_count - .with_label_values(&["streaming_read"]) - .inc(); - } + let resp = match self.obj_store_request(path, range.clone()).send().await { + Ok(resp) => resp, + Err(sdk_err) => { + return Err(set_error_should_retry::( + self.config.clone(), + sdk_err.into(), + )); + } + }; - Err(either::Left(err)) - } - } - }, - RetryCondition::new(&self.config.s3), - ) - .await?; let reader = FuturesStreamCompatByteStream::new(resp.body); Ok(Box::pin( @@ -479,7 +547,10 @@ impl ObjectStore for S3ObjectStore { .bucket(&self.bucket) .key(path) .send() - .await?; + .await + .map_err(|err| { + set_error_should_retry::(self.config.clone(), err.into()) + })?; Ok(()) } @@ -509,7 +580,9 @@ impl ObjectStore for S3ObjectStore { .delete_objects() .bucket(&self.bucket) .delete(delete_builder.build().unwrap()).send() - .await?; + .await.map_err(|err| { + set_error_should_retry::(self.config.clone(),err.into()) + })?; // Check if there were errors. if !delete_output.errors().is_empty() { @@ -531,6 +604,7 @@ impl ObjectStore for S3ObjectStore { self.client.clone(), self.bucket.clone(), prefix.to_string(), + self.config.clone(), ))) } @@ -544,19 +618,19 @@ impl S3ObjectStore { let mut http = hyper::client::HttpConnector::new(); // connection config - if let Some(keepalive_ms) = config.s3.object_store_keepalive_ms.as_ref() { + if let Some(keepalive_ms) = config.s3.keepalive_ms.as_ref() { http.set_keepalive(Some(Duration::from_millis(*keepalive_ms))); } - if let Some(nodelay) = config.s3.object_store_nodelay.as_ref() { + if let Some(nodelay) = config.s3.nodelay.as_ref() { http.set_nodelay(*nodelay); } - if let Some(recv_buffer_size) = config.s3.object_store_recv_buffer_size.as_ref() { + if let Some(recv_buffer_size) = config.s3.recv_buffer_size.as_ref() { http.set_recv_buffer_size(Some(*recv_buffer_size)); } - if let Some(send_buffer_size) = config.s3.object_store_send_buffer_size.as_ref() { + if let Some(send_buffer_size) = config.s3.send_buffer_size.as_ref() { http.set_send_buffer_size(Some(*send_buffer_size)); } @@ -579,11 +653,9 @@ impl S3ObjectStore { pub async fn new_with_config( bucket: String, metrics: Arc, - config: ObjectStoreConfig, + config: Arc, ) -> Self { - let sdk_config_loader = aws_config::from_env() - .retry_config(RetryConfig::standard().with_max_attempts(4)) - .http_client(Self::new_http_client(&config)); + let sdk_config_loader = aws_config::from_env().http_client(Self::new_http_client(&config)); // Retry 3 times if we get server-side errors or throttling errors let client = match std::env::var("RW_S3_ENDPOINT") { @@ -650,10 +722,10 @@ impl S3ObjectStore { } /// Creates a minio client. The server should be like `minio://key:secret@address:port/bucket`. - pub async fn with_minio( + pub async fn new_minio_engine( server: &str, metrics: Arc, - s3_object_store_config: ObjectStoreConfig, + object_store_config: Arc, ) -> Self { let server = server.strip_prefix("minio://").unwrap(); let (access_key_id, rest) = server.split_once(':').unwrap(); @@ -690,11 +762,11 @@ impl S3ObjectStore { .identity_cache( aws_sdk_s3::config::IdentityCache::lazy() .load_timeout(Duration::from_secs( - s3_object_store_config.s3.identity_resolution_timeout_s, + object_store_config.s3.identity_resolution_timeout_s, )) .build(), ) - .http_client(Self::new_http_client(&s3_object_store_config)) + .http_client(Self::new_http_client(&object_store_config)) .behavior_version_latest() .stalled_stream_protection(aws_sdk_s3::config::StalledStreamProtectionConfig::disabled()); let config = builder @@ -708,7 +780,7 @@ impl S3ObjectStore { bucket: bucket.to_string(), part_size: MINIO_PART_SIZE, metrics, - config: s3_object_store_config, + config: object_store_config, } } @@ -839,16 +911,6 @@ impl S3ObjectStore { } is_expiration_configured } - - #[inline(always)] - fn get_retry_strategy(&self) -> impl Iterator { - ExponentialBackoff::from_millis(self.config.s3.object_store_req_retry_interval_ms) - .max_delay(Duration::from_millis( - self.config.s3.object_store_req_retry_max_delay_ms, - )) - .take(self.config.s3.object_store_req_retry_max_attempts) - .map(jitter) - } } struct S3ObjectIter { @@ -862,16 +924,15 @@ struct S3ObjectIter { send_future: Option< BoxFuture< 'static, - Result< - (Vec, Option, Option), - SdkError>, - >, + Result<(Vec, Option, Option), ObjectError>, >, >, + + config: Arc, } impl S3ObjectIter { - fn new(client: Client, bucket: String, prefix: String) -> Self { + fn new(client: Client, bucket: String, prefix: String, config: Arc) -> Self { Self { buffer: VecDeque::default(), client, @@ -880,6 +941,7 @@ impl S3ObjectIter { next_continuation_token: None, is_truncated: Some(true), send_future: None, + config, } } } @@ -902,7 +964,7 @@ impl Stream for S3ObjectIter { } Err(e) => { self.send_future = None; - Poll::Ready(Some(Err(e.into()))) + Poll::Ready(Some(Err(e))) } }; } @@ -917,6 +979,7 @@ impl Stream for S3ObjectIter { if let Some(continuation_token) = self.next_continuation_token.as_ref() { request = request.continuation_token(continuation_token); } + let config = self.config.clone(); let f = async move { match request.send().await { Ok(r) => { @@ -936,7 +999,10 @@ impl Stream for S3ObjectIter { let next_continuation_token = r.next_continuation_token; Ok((more, next_continuation_token, is_truncated)) } - Err(e) => Err(e), + Err(e) => Err(set_error_should_retry::( + config, + e.into(), + )), } }; self.send_future = Some(Box::pin(f)); @@ -944,77 +1010,79 @@ impl Stream for S3ObjectIter { } } -type RetryError = Either< - SdkError>, - ByteStreamError, ->; - -impl From for ObjectError { - fn from(e: RetryError) -> Self { - match e { - Either::Left(e) => e.into(), - Either::Right(e) => e.into(), - } - } -} - -struct RetryCondition { - retry_unknown_service_error: bool, - retryable_service_error_codes: Vec, -} +fn set_error_should_retry(config: Arc, object_err: ObjectError) -> ObjectError +where + E: ProvideErrorMetadata + Into + Sync + Send + std::error::Error + 'static, +{ + let not_found = object_err.is_object_not_found_error(); -impl RetryCondition { - fn new(config: &S3ObjectStoreConfig) -> Self { - Self { - retry_unknown_service_error: config.developer.object_store_retry_unknown_service_error - || config.retry_unknown_service_error, - retryable_service_error_codes: config - .developer - .object_store_retryable_service_error_codes - .clone(), - } + if not_found { + return object_err; } -} -impl tokio_retry::Condition for RetryCondition { - fn should_retry(&mut self, err: &RetryError) -> bool { - match err { - Either::Left(err) => match err { - SdkError::DispatchFailure(e) => { + let mut inner = object_err.into_inner(); + match inner.borrow_mut() { + ObjectErrorInner::S3 { + should_retry, + inner, + } => { + let sdk_err = inner + .as_ref() + .downcast_ref::>>(); + + let err_should_retry = match sdk_err { + Some(SdkError::DispatchFailure(e)) => { if e.is_timeout() { tracing::warn!(target: "http_timeout_retry", "{e:?} occurs, retry S3 get_object request."); - return true; + true + } else { + false } } - SdkError::ServiceError(e) => match e.err().code() { - None => { - if self.retry_unknown_service_error { - tracing::warn!(target: "unknown_service_error", "{e:?} occurs, retry S3 get_object request."); - return true; + + Some(SdkError::ServiceError(e)) => { + let retry = match e.err().code() { + None => { + if config.s3.developer.retry_unknown_service_error + || config.s3.retry_unknown_service_error + { + tracing::warn!(target: "unknown_service_error", "{e:?} occurs, retry S3 get_object request."); + true + } else { + false + } } - } - Some(code) => { - if self - .retryable_service_error_codes - .iter() - .any(|s| s.as_str().eq_ignore_ascii_case(code)) - { - tracing::warn!(target: "retryable_service_error", "{e:?} occurs, retry S3 get_object request."); - return true; + Some(code) => { + if config + .s3 + .developer + .retryable_service_error_codes + .iter() + .any(|s| s.as_str().eq_ignore_ascii_case(code)) + { + tracing::warn!(target: "retryable_service_error", "{e:?} occurs, retry S3 get_object request."); + true + } else { + false + } } - } - }, - _ => {} - }, - Either::Right(_) => { - // Unfortunately `ErrorKind` of `ByteStreamError` is not accessible. - // Always returns true and relies on req_retry_max_attempts to avoid infinite loop. - return true; - } + }; + + retry + } + + Some(SdkError::TimeoutError(_err)) => true, + + _ => false, + }; + + *should_retry = err_should_retry; } - false + _ => unreachable!(), } + + ObjectError::from(inner) } #[cfg(test)] diff --git a/src/object_store/src/object/sim/mod.rs b/src/object_store/src/object/sim/mod.rs index 31f34cbcd3267..2f06b91839090 100644 --- a/src/object_store/src/object/sim/mod.rs +++ b/src/object_store/src/object/sim/mod.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg(madsim)] + mod client; mod error; use bytes::{BufMut, BytesMut}; @@ -33,8 +35,8 @@ use risingwave_common::range::RangeBoundsExt; use self::client::Client; use self::service::Response; use super::{ - BoxedStreamingUploader, Bytes, ObjectDataStream, ObjectError, ObjectMetadata, - ObjectMetadataIter, ObjectRangeBounds, ObjectResult, ObjectStore, StreamingUploader, + Bytes, ObjectDataStream, ObjectError, ObjectMetadata, ObjectMetadataIter, ObjectRangeBounds, + ObjectResult, ObjectStore, StreamingUploader, }; pub struct SimStreamingUploader { @@ -53,14 +55,13 @@ impl SimStreamingUploader { } } -#[async_trait::async_trait] impl StreamingUploader for SimStreamingUploader { async fn write_bytes(&mut self, data: Bytes) -> ObjectResult<()> { self.buf.put(data); Ok(()) } - async fn finish(mut self: Box) -> ObjectResult<()> { + async fn finish(mut self) -> ObjectResult<()> { if self.buf.is_empty() { Err(ObjectError::internal("upload empty object")) } else { @@ -115,7 +116,9 @@ pub struct SimObjectStore { #[async_trait::async_trait] impl ObjectStore for SimObjectStore { - fn get_object_prefix(&self, _obj_id: u64) -> String { + type StreamingUploader = SimStreamingUploader; + + fn get_object_prefix(&self, _obj_id: u64, _use_new_object_prefix_strategy: bool) -> String { String::default() } @@ -136,11 +139,11 @@ impl ObjectStore for SimObjectStore { } } - async fn streaming_upload(&self, path: &str) -> ObjectResult { - Ok(Box::new(SimStreamingUploader::new( + async fn streaming_upload(&self, path: &str) -> ObjectResult { + Ok(SimStreamingUploader::new( self.client.clone(), path.to_string(), - ))) + )) } async fn read(&self, path: &str, range: impl ObjectRangeBounds) -> ObjectResult { diff --git a/src/prost/build.rs b/src/prost/build.rs index e031e5cfb01ae..6d31201fa4733 100644 --- a/src/prost/build.rs +++ b/src/prost/build.rs @@ -53,6 +53,7 @@ fn main() -> Result<(), Box> { "task_service", "telemetry", "user", + "secret", ]; let protos: Vec = proto_files .iter() @@ -65,6 +66,18 @@ fn main() -> Result<(), Box> { ".plan_common.ExternalTableDesc", ".hummock.CompactTask", ".catalog.StreamSourceInfo", + ".catalog.SecretRef", + ".catalog.Source", + ".catalog.Sink", + ".catalog.View", + ".connector_service.ValidateSourceRequest", + ".connector_service.GetEventStreamRequest", + ".connector_service.SinkParam", + ".stream_plan.SinkDesc", + ".stream_plan.StreamFsFetch", + ".stream_plan.SourceBackfillNode", + ".stream_plan.StreamSource", + ".batch_plan.SourceNode", ]; // Build protobuf structs. @@ -99,6 +112,8 @@ fn main() -> Result<(), Box> { // The requirement is from Source node -> SourceCatalog -> WatermarkDesc -> expr .type_attribute("catalog.WatermarkDesc", "#[derive(Eq, Hash)]") .type_attribute("catalog.StreamSourceInfo", "#[derive(Eq, Hash)]") + .type_attribute("secret.SecretRef", "#[derive(Eq, Hash)]") + .type_attribute("catalog.IndexColumnProperties", "#[derive(Eq, Hash)]") .type_attribute("expr.ExprNode", "#[derive(Eq, Hash)]") .type_attribute("data.DataType", "#[derive(Eq, Hash)]") .type_attribute("expr.ExprNode.rex_node", "#[derive(Eq, Hash)]") @@ -138,6 +153,13 @@ fn main() -> Result<(), Box> { .type_attribute("plan_common.AdditionalColumnHeader", "#[derive(Eq, Hash)]") .type_attribute("plan_common.AdditionalColumnHeaders", "#[derive(Eq, Hash)]") .type_attribute("plan_common.AdditionalColumnOffset", "#[derive(Eq, Hash)]") + .type_attribute("plan_common.AdditionalDatabaseName", "#[derive(Eq, Hash)]") + .type_attribute("plan_common.AdditionalSchemaName", "#[derive(Eq, Hash)]") + .type_attribute("plan_common.AdditionalTableName", "#[derive(Eq, Hash)]") + .type_attribute( + "plan_common.AdditionalCollectionName", + "#[derive(Eq, Hash)]", + ) .type_attribute("common.ColumnOrder", "#[derive(Eq, Hash)]") .type_attribute("common.OrderType", "#[derive(Eq, Hash)]") .type_attribute("common.Buffer", "#[derive(Eq)]") diff --git a/src/prost/helpers/src/generate.rs b/src/prost/helpers/src/generate.rs index 432f233e74cb9..f674fec5aa2cf 100644 --- a/src/prost/helpers/src/generate.rs +++ b/src/prost/helpers/src/generate.rs @@ -17,8 +17,8 @@ use quote::quote; use syn::ext::IdentExt; use syn::spanned::Spanned; use syn::{ - self, Error, Expr, ExprLit, Field, GenericArgument, Lit, Meta, PathArguments, PathSegment, - Result, Type, + Error, Expr, ExprLit, Field, GenericArgument, Lit, Meta, PathArguments, PathSegment, Result, + Type, }; fn extract_type_from_option(option_segment: &PathSegment) -> Type { diff --git a/src/prost/src/lib.rs b/src/prost/src/lib.rs index 825e721891fed..2a20f2e5530d4 100644 --- a/src/prost/src/lib.rs +++ b/src/prost/src/lib.rs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// for derived code of `Message` #![expect(clippy::all)] #![expect(clippy::doc_markdown)] -#![allow(non_snake_case)] // for derived code of `Message` #![feature(lint_reasons)] use std::str::FromStr; @@ -56,7 +56,7 @@ pub mod batch_plan; #[cfg_attr(madsim, path = "sim/task_service.rs")] pub mod task_service; #[rustfmt::skip] -#[cfg_attr(madsim, path="sim/connector_service.rs")] +#[cfg_attr(madsim, path = "sim/connector_service.rs")] pub mod connector_service; #[rustfmt::skip] #[cfg_attr(madsim, path = "sim/stream_plan.rs")] @@ -91,6 +91,10 @@ pub mod health; #[rustfmt::skip] #[path = "sim/telemetry.rs"] pub mod telemetry; + +#[rustfmt::skip] +#[path = "sim/secret.rs"] +pub mod secret; #[rustfmt::skip] #[path = "connector_service.serde.rs"] pub mod connector_service_serde; @@ -158,6 +162,10 @@ pub mod java_binding_serde; #[path = "telemetry.serde.rs"] pub mod telemetry_serde; +#[rustfmt::skip] +#[path = "secret.serde.rs"] +pub mod secret_serde; + #[derive(Clone, PartialEq, Eq, Debug, Error)] #[error("field `{0}` not found")] pub struct PbFieldNotFound(pub &'static str); @@ -196,6 +204,13 @@ impl stream_plan::MaterializeNode { } } +// Encapsulating the use of parallel_units. +impl common::WorkerNode { + pub fn parallelism(&self) -> usize { + self.parallel_units.len() + } +} + impl stream_plan::SourceNode { pub fn column_ids(&self) -> Option> { Some( diff --git a/src/risedevtool/Cargo.toml b/src/risedevtool/Cargo.toml index 78ec4b5a63e45..b8a2ca9db14f9 100644 --- a/src/risedevtool/Cargo.toml +++ b/src/risedevtool/Cargo.toml @@ -23,7 +23,7 @@ clap = { workspace = true } console = "0.15" fs-err = "2.11.0" glob = "0.3" -google-cloud-pubsub = "0.24" +google-cloud-pubsub = "0.25" indicatif = "0.17" itertools = { workspace = true } rdkafka = { workspace = true } diff --git a/src/risedevtool/common.toml b/src/risedevtool/common.toml index 97ddcd0485f9d..391d52f399cf9 100644 --- a/src/risedevtool/common.toml +++ b/src/risedevtool/common.toml @@ -5,7 +5,6 @@ OS = { source = "${CARGO_MAKE_RUST_TARGET_OS}", mapping = { linux = "linux", mac ARCH = { source = "${CARGO_MAKE_RUST_TARGET_ARCH}", mapping = { x86_64 = "amd64", aarch64 = "arm64" } } SYSTEM = "${OS}-${ARCH}" SYSTEM_UNDERSCORE = "${OS}_${ARCH}" -SYSTEM_AMD64 = "${OS}-amd64" # some components do not support darwin-arm64 for now, use amd64 for fallback PREFIX = "${PWD}/.risingwave" JAVA_DIR = "${PWD}/java" PREFIX_USR_BIN = "${PWD}/.bin" diff --git a/src/risedevtool/config/src/main.rs b/src/risedevtool/config/src/main.rs index 7d36cc8d4646f..27a676c2bfb3e 100644 --- a/src/risedevtool/config/src/main.rs +++ b/src/risedevtool/config/src/main.rs @@ -62,7 +62,6 @@ pub enum Components { Hdfs, PrometheusAndGrafana, Etcd, - Kafka, Pubsub, Redis, Tracing, @@ -74,6 +73,12 @@ pub enum Components { DynamicLinking, HummockTrace, Coredump, + NoBacktrace, + ExternalUdf, + WasmUdf, + JsUdf, + DenoUdf, + PythonUdf, } impl Components { @@ -83,7 +88,6 @@ impl Components { Self::Hdfs => "[Component] Hummock: Hdfs Backend", Self::PrometheusAndGrafana => "[Component] Metrics: Prometheus + Grafana", Self::Etcd => "[Component] Etcd", - Self::Kafka => "[Component] Kafka", Self::Pubsub => "[Component] Google Pubsub", Self::Redis => "[Component] Redis", Self::BuildConnectorNode => "[Build] Build RisingWave Connector (Java)", @@ -95,6 +99,12 @@ impl Components { Self::DynamicLinking => "[Build] Enable dynamic linking", Self::HummockTrace => "[Build] Hummock Trace", Self::Coredump => "[Runtime] Enable coredump", + Self::NoBacktrace => "[Runtime] Disable backtrace", + Self::ExternalUdf => "[Build] Enable external UDF", + Self::WasmUdf => "[Build] Enable Wasm UDF", + Self::JsUdf => "[Build] Enable JS UDF", + Self::DenoUdf => "[Build] Enable Deno UDF", + Self::PythonUdf => "[Build] Enable Python UDF", } .into() } @@ -118,11 +128,6 @@ Required if you want to view metrics." Required if you want to persistent meta-node data. " } - Self::Kafka => { - " -Required if you want to create source from Kafka. - " - } Self::Pubsub => { " Required if you want to create source from Emulated Google Pub/sub. @@ -192,6 +197,16 @@ the binaries will also be codesigned with `get-task-allow` enabled. As a result, RisingWave will dump the core on panics. " } + Self::NoBacktrace => { + " +With this option enabled, RiseDev will not set `RUST_BACKTRACE` when launching nodes. + " + } + Self::ExternalUdf => "Required if you want to support external UDF.", + Self::WasmUdf => "Required if you want to support WASM UDF.", + Self::JsUdf => "Required if you want to support JS UDF.", + Self::DenoUdf => "Required if you want to support Deno UDF.", + Self::PythonUdf => "Required if you want to support Python UDF.", } .into() } @@ -202,7 +217,6 @@ As a result, RisingWave will dump the core on panics. "ENABLE_HDFS" => Some(Self::Hdfs), "ENABLE_PROMETHEUS_GRAFANA" => Some(Self::PrometheusAndGrafana), "ENABLE_ETCD" => Some(Self::Etcd), - "ENABLE_KAFKA" => Some(Self::Kafka), "ENABLE_PUBSUB" => Some(Self::Pubsub), "ENABLE_BUILD_RUST" => Some(Self::RustComponents), "ENABLE_BUILD_DASHBOARD" => Some(Self::Dashboard), @@ -213,6 +227,13 @@ As a result, RisingWave will dump the core on panics. "ENABLE_REDIS" => Some(Self::Redis), "ENABLE_BUILD_RW_CONNECTOR" => Some(Self::BuildConnectorNode), "ENABLE_HUMMOCK_TRACE" => Some(Self::HummockTrace), + "ENABLE_COREDUMP" => Some(Self::Coredump), + "DISABLE_BACKTRACE" => Some(Self::NoBacktrace), + "ENABLE_EXTERNAL_UDF" => Some(Self::ExternalUdf), + "ENABLE_WASM_UDF" => Some(Self::WasmUdf), + "ENABLE_JS_UDF" => Some(Self::JsUdf), + "ENABLE_DENO_UDF" => Some(Self::DenoUdf), + "ENABLE_PYTHON_UDF" => Some(Self::PythonUdf), _ => None, } } @@ -223,7 +244,6 @@ As a result, RisingWave will dump the core on panics. Self::Hdfs => "ENABLE_HDFS", Self::PrometheusAndGrafana => "ENABLE_PROMETHEUS_GRAFANA", Self::Etcd => "ENABLE_ETCD", - Self::Kafka => "ENABLE_KAFKA", Self::Pubsub => "ENABLE_PUBSUB", Self::Redis => "ENABLE_REDIS", Self::RustComponents => "ENABLE_BUILD_RUST", @@ -235,6 +255,12 @@ As a result, RisingWave will dump the core on panics. Self::DynamicLinking => "ENABLE_DYNAMIC_LINKING", Self::HummockTrace => "ENABLE_HUMMOCK_TRACE", Self::Coredump => "ENABLE_COREDUMP", + Self::NoBacktrace => "DISABLE_BACKTRACE", + Self::ExternalUdf => "ENABLE_EXTERNAL_UDF", + Self::WasmUdf => "ENABLE_WASM_UDF", + Self::JsUdf => "ENABLE_JS_UDF", + Self::DenoUdf => "ENABLE_DENO_UDF", + Self::PythonUdf => "ENABLE_PYTHON_UDF", } .into() } diff --git a/src/risedevtool/connector.toml b/src/risedevtool/connector.toml index abc23b67abdf8..535b4db843172 100644 --- a/src/risedevtool/connector.toml +++ b/src/risedevtool/connector.toml @@ -17,7 +17,7 @@ script = ''' set -euo pipefail -if !(command -v javac &> /dev/null && [[ "$(javac -version 2>&1 | awk '{print $2}')" =~ ^(11|17|19|20|21) ]]); then +if !(command -v javac &> /dev/null && [[ "$(javac -version 2>&1 | awk '{print $2}')" =~ ^(11|17|19|20|21|22) ]]); then echo "JDK 11+ is not installed. Please install JDK 11+ first." exit 1 fi diff --git a/src/risedevtool/gcloud-pubsub.toml b/src/risedevtool/gcloud-pubsub.toml index 313561b287cba..85b5eae3e170a 100644 --- a/src/risedevtool/gcloud-pubsub.toml +++ b/src/risedevtool/gcloud-pubsub.toml @@ -2,14 +2,16 @@ extend = "common.toml" [env] GCLOUD_DOWNLOAD_PATH = "${PREFIX_TMP}/gcloud.tgz" -GCLOUD_DOWNLOAD_TGZ = { value = "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-406.0.0-linux-x86_64.tar.gz", condition = { env_not_set = [ "GCLOUD_DOWNLOAD_TGZ" ] } } +GCLOUD_DOWNLOAD_TGZ = { value = "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-475.0.0-linux-x86_64.tar.gz", condition = { env_not_set = [ + "GCLOUD_DOWNLOAD_TGZ", +] } } GCLOUD_SDK_DIR = "google-cloud-sdk" [tasks.download-pubsub] private = true category = "RiseDev - Components" dependencies = ["prepare"] -condition = { env_set = [ "ENABLE_PUBSUB" ] } +condition = { env_set = ["ENABLE_PUBSUB"] } description = "Download and enable Google Pubsub Emulator" script = ''' #!/usr/bin/env bash diff --git a/src/risedevtool/grafana.toml b/src/risedevtool/grafana.toml index e64524aa4ba25..10d30c53c5fa5 100644 --- a/src/risedevtool/grafana.toml +++ b/src/risedevtool/grafana.toml @@ -1,9 +1,9 @@ extend = "common.toml" [env] -GRAFANA_SYSTEM = "${SYSTEM_AMD64}" +GRAFANA_SYSTEM = "${SYSTEM}" GRAFANA_DOWNLOAD_PATH = "${PREFIX_TMP}/grafana.tar.gz" -GRAFANA_VERSION = "10.0.0" +GRAFANA_VERSION = "10.4.3" GRAFANA_RELEASE = "grafana-${GRAFANA_VERSION}" GRAFANA_DOWNLOAD_TAR_GZ = "https://dl.grafana.com/oss/release/${GRAFANA_RELEASE}.${GRAFANA_SYSTEM}.tar.gz" @@ -25,7 +25,7 @@ echo "Grafana not found, download ${GRAFANA_RELEASE}" curl -fL -o "${GRAFANA_DOWNLOAD_PATH}" "${GRAFANA_DOWNLOAD_TAR_GZ}" tar -xf "${GRAFANA_DOWNLOAD_PATH}" -C "${PREFIX_TMP}" rm -rf "${PREFIX_BIN}/grafana" -mv "${PREFIX_TMP}/${GRAFANA_RELEASE}" "${PREFIX_BIN}/grafana" +mv "${PREFIX_TMP}/grafana-v${GRAFANA_VERSION}" "${PREFIX_BIN}/grafana" rm "${GRAFANA_DOWNLOAD_PATH}" touch "${PREFIX_BIN}/grafana/RISEDEV-VERSION-${GRAFANA_VERSION}" ''' diff --git a/src/risedevtool/kafka.toml b/src/risedevtool/kafka.toml deleted file mode 100644 index 4353e30aef919..0000000000000 --- a/src/risedevtool/kafka.toml +++ /dev/null @@ -1,32 +0,0 @@ -extend = "common.toml" - -[env] -KAFKA_DOWNLOAD_PATH = "${PREFIX_TMP}/kafka.tgz" - -[tasks.download-kafka] -private = true -category = "RiseDev - Components" -dependencies = ["prepare"] -condition = { env_set = [ "ENABLE_KAFKA" ] } -description = "Download and extract Kafka" -script = ''' -#!/usr/bin/env bash -set -e -if [ -d "${PREFIX_BIN}/kafka" ]; then - exit 0 -fi - -get_latest_kafka_version() { - local versions=$(curl -s https://downloads.apache.org/kafka/ | grep -Eo 'href="[0-9]+\.[0-9]+\.[0-9]+/"' | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+") - # Sort the version numbers and get the latest one - local latest_version=$(echo "$versions" | sort -V | tail -n1) - echo $latest_version -} - -echo "Kafka not found, downloading..." -latest_version=$(get_latest_kafka_version) -curl -fL -o "${KAFKA_DOWNLOAD_PATH}" "https://downloads.apache.org/kafka/${latest_version}/kafka_2.13-${latest_version}.tgz" -tar -xf "${KAFKA_DOWNLOAD_PATH}" -C "${PREFIX_TMP}" -mv "${PREFIX_TMP}/kafka_2.13-${latest_version}" "${PREFIX_BIN}/kafka" -rm ${KAFKA_DOWNLOAD_PATH} -''' diff --git a/src/risedevtool/src/bin/risedev-compose.rs b/src/risedevtool/src/bin/risedev-compose.rs index 0f3cb5b020fca..89bb0592d0d85 100644 --- a/src/risedevtool/src/bin/risedev-compose.rs +++ b/src/risedevtool/src/bin/risedev-compose.rs @@ -19,7 +19,7 @@ use std::path::Path; use anyhow::{anyhow, Result}; use clap::Parser; use console::style; -use fs_err::{self, File}; +use fs_err::File; use itertools::Itertools; use risedev::{ compose_deploy, generate_risedev_env, Compose, ComposeConfig, ComposeDeployConfig, ComposeFile, @@ -201,9 +201,6 @@ fn main() -> Result<()> { ServiceConfig::Pubsub(_) => { return Err(anyhow!("not supported, please use redpanda instead")) } - ServiceConfig::ZooKeeper(_) => { - return Err(anyhow!("not supported, please use redpanda instead")) - } ServiceConfig::Opendal(_) => continue, ServiceConfig::AwsS3(_) => continue, ServiceConfig::RedPanda(c) => { @@ -222,7 +219,10 @@ fn main() -> Result<()> { volumes.insert(c.id.clone(), ComposeVolume::default()); (c.address.clone(), c.compose(&compose_config)?) } - ServiceConfig::Redis(_) => return Err(anyhow!("not supported")), + ServiceConfig::Redis(_) + | ServiceConfig::MySql(_) + | ServiceConfig::Postgres(_) + | ServiceConfig::SchemaRegistry(_) => return Err(anyhow!("not supported")), }; compose.container_name = service.id().to_string(); if opts.deploy { @@ -245,7 +245,6 @@ fn main() -> Result<()> { } }); let compose_file = ComposeFile { - version: "3".into(), services: services.clone(), volumes: node_volumes, name: format!("risingwave-{}", opts.profile), @@ -303,7 +302,6 @@ fn main() -> Result<()> { } } let compose_file = ComposeFile { - version: "3".into(), services, volumes, name: format!("risingwave-{}", opts.profile), diff --git a/src/risedevtool/src/bin/risedev-dev.rs b/src/risedevtool/src/bin/risedev-dev.rs index d5a14cbf8c1bd..02cef8655db2f 100644 --- a/src/risedevtool/src/bin/risedev-dev.rs +++ b/src/risedevtool/src/bin/risedev-dev.rs @@ -24,11 +24,11 @@ use fs_err::OpenOptions; use indicatif::ProgressBar; use risedev::util::{complete_spin, fail_spin}; use risedev::{ - generate_risedev_env, preflight_check, AwsS3Config, CompactorService, ComputeNodeService, - ConfigExpander, ConfigureTmuxTask, EnsureStopService, ExecuteContext, FrontendService, - GrafanaService, KafkaService, MetaNodeService, MinioService, OpendalConfig, PrometheusService, - PubsubService, RedisService, ServiceConfig, SqliteConfig, Task, TempoService, ZooKeeperService, - RISEDEV_NAME, + generate_risedev_env, preflight_check, CompactorService, ComputeNodeService, ConfigExpander, + ConfigureTmuxTask, DummyService, EnsureStopService, ExecuteContext, FrontendService, + GrafanaService, KafkaService, MetaNodeService, MinioService, MySqlService, PostgresService, + PrometheusService, PubsubService, RedisService, SchemaRegistryService, ServiceConfig, + SqliteConfig, Task, TempoService, RISEDEV_NAME, }; use tempfile::tempdir; use thiserror_ext::AsReport; @@ -135,8 +135,7 @@ fn task_main( // let mut task = risedev::EtcdReadyCheckTask::new(c.clone())?; // TODO(chi): etcd will set its health check to success only after all nodes are // connected and there's a leader, therefore we cannot do health check for now. - let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, false)?; + let mut task = risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, false)?; task.execute(&mut ctx)?; } ServiceConfig::Sqlite(c) => { @@ -172,8 +171,7 @@ fn task_main( ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); let mut service = PrometheusService::new(c.clone())?; service.execute(&mut ctx)?; - let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, false)?; + let mut task = risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, false)?; task.execute(&mut ctx)?; ctx.pb .set_message(format!("api http://{}:{}/", c.address, c.port)); @@ -185,7 +183,7 @@ fn task_main( service.execute(&mut ctx)?; let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, c.user_managed)?; + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; task.execute(&mut ctx)?; ctx.pb .set_message(format!("api grpc://{}:{}/", c.address, c.port)); @@ -196,7 +194,7 @@ fn task_main( let mut service = MetaNodeService::new(c.clone())?; service.execute(&mut ctx)?; let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, c.user_managed)?; + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; task.execute(&mut ctx)?; ctx.pb.set_message(format!( "api grpc://{}:{}/, dashboard http://{}:{}/", @@ -209,7 +207,7 @@ fn task_main( let mut service = FrontendService::new(c.clone())?; service.execute(&mut ctx)?; let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, c.user_managed)?; + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; task.execute(&mut ctx)?; ctx.pb .set_message(format!("api postgres://{}:{}/", c.address, c.port)); @@ -231,7 +229,7 @@ fn task_main( let mut service = CompactorService::new(c.clone())?; service.execute(&mut ctx)?; let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, c.user_managed)?; + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; task.execute(&mut ctx)?; ctx.pb .set_message(format!("compactor {}:{}", c.address, c.port)); @@ -241,8 +239,7 @@ fn task_main( ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); let mut service = GrafanaService::new(c.clone())?; service.execute(&mut ctx)?; - let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, false)?; + let mut task = risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, false)?; task.execute(&mut ctx)?; ctx.pb .set_message(format!("dashboard http://{}:{}/", c.address, c.port)); @@ -253,7 +250,7 @@ fn task_main( let mut service = TempoService::new(c.clone())?; service.execute(&mut ctx)?; let mut task = - risedev::ConfigureGrpcNodeTask::new(c.listen_address.clone(), c.port, false)?; + risedev::TcpReadyCheckTask::new(c.listen_address.clone(), c.port, false)?; task.execute(&mut ctx)?; ctx.pb .set_message(format!("api http://{}:{}/", c.listen_address, c.port)); @@ -261,70 +258,39 @@ fn task_main( ServiceConfig::AwsS3(c) => { let mut ctx = ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); - - struct AwsService(AwsS3Config); - impl Task for AwsService { - fn execute( - &mut self, - _ctx: &mut ExecuteContext, - ) -> anyhow::Result<()> { - Ok(()) - } - - fn id(&self) -> String { - self.0.id.clone() - } - } - - ctx.service(&AwsService(c.clone())); - ctx.complete_spin(); + DummyService::new(&c.id).execute(&mut ctx)?; ctx.pb .set_message(format!("using AWS s3 bucket {}", c.bucket)); } ServiceConfig::Opendal(c) => { let mut ctx = ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); - - struct OpendalService(OpendalConfig); - impl Task for OpendalService { - fn execute( - &mut self, - _ctx: &mut ExecuteContext, - ) -> anyhow::Result<()> { - Ok(()) - } - - fn id(&self) -> String { - self.0.id.clone() - } - } - - ctx.service(&OpendalService(c.clone())); - ctx.complete_spin(); + DummyService::new(&c.id).execute(&mut ctx)?; ctx.pb .set_message(format!("using Opendal, namenode = {}", c.namenode)); } - ServiceConfig::ZooKeeper(c) => { + ServiceConfig::Kafka(c) => { let mut ctx = ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); - let mut service = ZooKeeperService::new(c.clone())?; + let mut service = KafkaService::new(c.clone()); service.execute(&mut ctx)?; - let mut task = - risedev::ConfigureGrpcNodeTask::new(c.address.clone(), c.port, false)?; + let mut task = risedev::KafkaReadyCheckTask::new(c.clone())?; task.execute(&mut ctx)?; ctx.pb - .set_message(format!("zookeeper {}:{}", c.address, c.port)); + .set_message(format!("kafka {}:{}", c.address, c.port)); } - ServiceConfig::Kafka(c) => { + ServiceConfig::SchemaRegistry(c) => { let mut ctx = ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); - let mut service = KafkaService::new(c.clone())?; + let mut service = SchemaRegistryService::new(c.clone()); service.execute(&mut ctx)?; - let mut task = risedev::KafkaReadyCheckTask::new(c.clone())?; + let mut task = + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; task.execute(&mut ctx)?; ctx.pb - .set_message(format!("kafka {}:{}", c.address, c.port)); + .set_message(format!("schema registry http://{}:{}", c.address, c.port)); } + ServiceConfig::Pubsub(c) => { let mut ctx = ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); @@ -348,6 +314,39 @@ fn task_main( ctx.pb .set_message(format!("redis {}:{}", c.address, c.port)); } + ServiceConfig::MySql(c) => { + let mut ctx = + ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); + MySqlService::new(c.clone()).execute(&mut ctx)?; + if c.user_managed { + let mut task = + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; + task.execute(&mut ctx)?; + } else { + // When starting a MySQL container, the MySQL process is set as the main process. + // Since the first process in a container always gets PID 1, the MySQL log always shows + // "starting as process 1". + let mut task = risedev::LogReadyCheckTask::new("starting as process 1\n")?; + task.execute(&mut ctx)?; + } + ctx.pb + .set_message(format!("mysql {}:{}", c.address, c.port)); + } + ServiceConfig::Postgres(c) => { + let mut ctx = + ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); + PostgresService::new(c.clone()).execute(&mut ctx)?; + if c.user_managed { + let mut task = + risedev::TcpReadyCheckTask::new(c.address.clone(), c.port, c.user_managed)?; + task.execute(&mut ctx)?; + } else { + let mut task = risedev::LogReadyCheckTask::new("ready to accept connections")?; + task.execute(&mut ctx)?; + } + ctx.pb + .set_message(format!("postgres {}:{}", c.address, c.port)); + } } let service_id = service.id().to_string(); diff --git a/src/risedevtool/src/compose.rs b/src/risedevtool/src/compose.rs index 779ca23557622..a490560157527 100644 --- a/src/risedevtool/src/compose.rs +++ b/src/risedevtool/src/compose.rs @@ -56,7 +56,6 @@ pub struct HealthCheck { #[derive(Debug, Clone, Serialize)] pub struct ComposeFile { - pub version: String, pub services: BTreeMap, pub volumes: BTreeMap, pub name: String, diff --git a/src/risedevtool/src/config.rs b/src/risedevtool/src/config.rs index d5fe2d8c220a5..bf768f8e68cd1 100644 --- a/src/risedevtool/src/config.rs +++ b/src/risedevtool/src/config.rs @@ -172,8 +172,12 @@ impl ConfigExpander { "kafka" => ServiceConfig::Kafka(serde_yaml::from_str(&out_str)?), "pubsub" => ServiceConfig::Pubsub(serde_yaml::from_str(&out_str)?), "redis" => ServiceConfig::Redis(serde_yaml::from_str(&out_str)?), - "zookeeper" => ServiceConfig::ZooKeeper(serde_yaml::from_str(&out_str)?), "redpanda" => ServiceConfig::RedPanda(serde_yaml::from_str(&out_str)?), + "mysql" => ServiceConfig::MySql(serde_yaml::from_str(&out_str)?), + "postgres" => ServiceConfig::Postgres(serde_yaml::from_str(&out_str)?), + "schema-registry" => { + ServiceConfig::SchemaRegistry(serde_yaml::from_str(&out_str)?) + } other => return Err(anyhow!("unsupported use type: {}", other)), }; Ok(result) diff --git a/src/risedevtool/src/config_gen.rs b/src/risedevtool/src/config_gen.rs index 32678c8a94549..5b931a5d238cc 100644 --- a/src/risedevtool/src/config_gen.rs +++ b/src/risedevtool/src/config_gen.rs @@ -16,9 +16,5 @@ mod prometheus_gen; pub use prometheus_gen::*; mod grafana_gen; pub use grafana_gen::*; -mod zookeeper_gen; -pub use zookeeper_gen::*; -mod kafka_gen; -pub use kafka_gen::*; mod tempo_gen; pub use tempo_gen::*; diff --git a/src/risedevtool/src/config_gen/kafka_gen.rs b/src/risedevtool/src/config_gen/kafka_gen.rs deleted file mode 100644 index 5f54efd5a4e44..0000000000000 --- a/src/risedevtool/src/config_gen/kafka_gen.rs +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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 itertools::Itertools; - -use crate::KafkaConfig; - -pub struct KafkaGen; - -impl KafkaGen { - pub fn gen_server_properties(&self, config: &KafkaConfig, kafka_log_dirs: &str) -> String { - let kafka_listen_host = &config.listen_address; - let kafka_advertise_host = &config.address; - let kafka_port = &config.port; - let zookeeper_hosts = config - .provide_zookeeper - .as_ref() - .unwrap() - .iter() - .map(|node| format!("{}:{}", node.address, node.port)) - .join(","); - let kafka_broker_id = config.broker_id; - - format!( - r#"# --- THIS FILE IS AUTO GENERATED BY RISEDEV --- - -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. - -# see kafka.server.KafkaConfig for additional details and defaults - -############################# Server Basics ############################# - -# The id of the broker. This must be set to a unique integer for each broker. -broker.id={kafka_broker_id} - -############################# Socket Server Settings ############################# - -# The address the socket server listens on. It will get the value returned from -# java.net.InetAddress.getCanonicalHostName() if not configured. -# FORMAT: -# listeners = listener_name://host_name:port -# EXAMPLE: -# listeners = PLAINTEXT://your.host.name:9092 -listeners=PLAINTEXT://{kafka_listen_host}:{kafka_port} - -# Hostname and port the broker will advertise to producers and consumers. If not set, -# it uses the value for "listeners" if configured. Otherwise, it will use the value -# returned from java.net.InetAddress.getCanonicalHostName(). -advertised.listeners=PLAINTEXT://{kafka_advertise_host}:{kafka_port} - -# Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details -#listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL - -# The number of threads that the server uses for receiving requests from the network and sending responses to the network -num.network.threads=3 - -# The number of threads that the server uses for processing requests, which may include disk I/O -num.io.threads=8 - -# The send buffer (SO_SNDBUF) used by the socket server -socket.send.buffer.bytes=102400 - -# The receive buffer (SO_RCVBUF) used by the socket server -socket.receive.buffer.bytes=102400 - -# The maximum size of a request that the socket server will accept (protection against OOM) -socket.request.max.bytes=104857600 - -############################# Log Basics ############################# - -# A comma separated list of directories under which to store log files -log.dirs={kafka_log_dirs} - -# The default number of log partitions per topic. More partitions allow greater -# parallelism for consumption, but this will also result in more files across -# the brokers. -num.partitions=1 - -# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown. -# This value is recommended to be increased for installations with data dirs located in RAID array. -num.recovery.threads.per.data.dir=1 - -############################# Internal Topic Settings ############################# -# The replication factor for the group metadata internal topics "__consumer_offsets" and "__transaction_state" -# For anything other than development testing, a value greater than 1 is recommended to ensure availability such as 3. -offsets.topic.replication.factor=1 -transaction.state.log.replication.factor=1 -transaction.state.log.min.isr=1 - -############################# Log Flush Policy ############################# - -# Messages are immediately written to the filesystem but by default we only fsync() to sync -# the OS cache lazily. The following configurations control the flush of data to disk. -# There are a few important trade-offs here: -# 1. Durability: Unflushed data may be lost if you are not using replication. -# 2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush. -# 3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to excessive seeks. -# The settings below allow one to configure the flush policy to flush data after a period of time or -# every N messages (or both). This can be done globally and overridden on a per-topic basis. - -# The number of messages to accept before forcing a flush of data to disk -#log.flush.interval.messages=10000 - -# The maximum amount of time a message can sit in a log before we force a flush -#log.flush.interval.ms=1000 - -############################# Log Retention Policy ############################# - -# The following configurations control the disposal of log segments. The policy can -# be set to delete segments after a period of time, or after a given size has accumulated. -# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens -# from the end of the log. - -# The minimum age of a log file to be eligible for deletion due to age -log.retention.hours=168 - -# A size-based retention policy for logs. Segments are pruned from the log unless the remaining -# segments drop below log.retention.bytes. Functions independently of log.retention.hours. -#log.retention.bytes=1073741824 - -# The maximum size of a log segment file. When this size is reached a new log segment will be created. -log.segment.bytes=1073741824 - -# The interval at which log segments are checked to see if they can be deleted according -# to the retention policies -log.retention.check.interval.ms=300000 - -############################# Zookeeper ############################# - -# Zookeeper connection string (see zookeeper docs for details). -# This is a comma separated host:port pairs, each corresponding to a zk -# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002". -# You can also append an optional chroot string to the urls to specify the -# root directory for all kafka znodes. -zookeeper.connect={zookeeper_hosts} - -# Timeout in ms for connecting to zookeeper -zookeeper.connection.timeout.ms=18000 - - -############################# Group Coordinator Settings ############################# - -# The following configuration specifies the time, in milliseconds, that the GroupCoordinator will delay the initial consumer rebalance. -# The rebalance will be further delayed by the value of group.initial.rebalance.delay.ms as new members join the group, up to a maximum of max.poll.interval.ms. -# The default value for this is 3 seconds. -# We override this to 0 here as it makes for a better out-of-the-box experience for development and testing. -# However, in production environments the default value of 3 seconds is more suitable as this will help to avoid unnecessary, and potentially expensive, rebalances during application startup. -group.initial.rebalance.delay.ms=0 -"# - ) - } -} diff --git a/src/risedevtool/src/config_gen/zookeeper_gen.rs b/src/risedevtool/src/config_gen/zookeeper_gen.rs deleted file mode 100644 index ed340d58db1c9..0000000000000 --- a/src/risedevtool/src/config_gen/zookeeper_gen.rs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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 crate::ZooKeeperConfig; - -pub struct ZooKeeperGen; - -impl ZooKeeperGen { - pub fn gen_server_properties( - &self, - config: &ZooKeeperConfig, - zookeeper_data_dir: &str, - ) -> String { - let zookeeper_listen_host = &config.listen_address; - let zookeeper_port = &config.port; - - format!( - r#"# --- THIS FILE IS AUTO GENERATED BY RISEDEV --- - -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You 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. -# the directory where the snapshot is stored. -dataDir={zookeeper_data_dir} -# the port at which the clients will connect -clientPort={zookeeper_port} -clientPortAddress={zookeeper_listen_host} -# disable the per-ip limit on the number of connections since this is a non-production config -maxClientCnxns=0 -# Disable the adminserver by default to avoid port conflicts. -# Set the port to something non-conflicting if choosing to enable this -admin.enableServer=false -# admin.serverPort=8080 -"# - ) - } -} diff --git a/src/risedevtool/src/risedev_env.rs b/src/risedevtool/src/risedev_env.rs index 1efdf1470998d..bff4062f72097 100644 --- a/src/risedevtool/src/risedev_env.rs +++ b/src/risedevtool/src/risedev_env.rs @@ -17,7 +17,7 @@ use std::fmt::Write; use std::process::Command; -use crate::{add_hummock_backend, HummockInMemoryStrategy, ServiceConfig}; +use crate::{add_hummock_backend, Application, HummockInMemoryStrategy, ServiceConfig}; /// Generate environment variables (put in file `.risingwave/config/risedev-env`) /// from the given service configurations to be used by future @@ -77,6 +77,45 @@ pub fn generate_risedev_env(services: &Vec) -> String { writeln!(env, r#"RISEDEV_KAFKA_WITH_OPTIONS_COMMON="connector='kafka',properties.bootstrap.server='{brokers}'""#).unwrap(); writeln!(env, r#"RPK_BROKERS="{brokers}""#).unwrap(); } + ServiceConfig::SchemaRegistry(c) => { + let url = format!("http://{}:{}", c.address, c.port); + writeln!(env, r#"RISEDEV_SCHEMA_REGISTRY_URL="{url}""#,).unwrap(); + writeln!(env, r#"RPK_REGISTRY_HOSTS="{url}""#).unwrap(); + } + ServiceConfig::MySql(c) if c.application != Application::Metastore => { + let host = &c.address; + let port = &c.port; + let user = &c.user; + let password = &c.password; + // These envs are used by `mysql` cli. + writeln!(env, r#"MYSQL_HOST="{host}""#,).unwrap(); + writeln!(env, r#"MYSQL_TCP_PORT="{port}""#,).unwrap(); + // Note: There's no env var for the username read by `mysql` cli. Here we set + // `RISEDEV_MYSQL_USER`, which will be read by `e2e_test/commands/mysql` when + // running `risedev slt`, as a wrapper of `mysql` cli. + writeln!(env, r#"RISEDEV_MYSQL_USER="{user}""#,).unwrap(); + writeln!(env, r#"MYSQL_PWD="{password}""#,).unwrap(); + // Note: user and password are not included in the common WITH options. + // It's expected to create another dedicated user for the source. + writeln!(env, r#"RISEDEV_MYSQL_WITH_OPTIONS_COMMON="connector='mysql-cdc',hostname='{host}',port='{port}'""#,).unwrap(); + } + ServiceConfig::Pubsub(c) => { + let address = &c.address; + let port = &c.port; + writeln!(env, r#"PUBSUB_EMULATOR_HOST="{address}:{port}""#,).unwrap(); + writeln!(env, r#"RISEDEV_PUBSUB_WITH_OPTIONS_COMMON="connector='google_pubsub',pubsub.emulator_host='{address}:{port}'""#,).unwrap(); + } + ServiceConfig::Postgres(c) if c.application != Application::Metastore => { + let host = &c.address; + let port = &c.port; + let user = &c.user; + let password = &c.password; + // These envs are used by `postgres` cli. + writeln!(env, r#"PGHOST="{host}""#,).unwrap(); + writeln!(env, r#"PGPORT="{port}""#,).unwrap(); + writeln!(env, r#"PGUSER="{user}""#,).unwrap(); + writeln!(env, r#"PGPASSWORD="{password}""#,).unwrap(); + } _ => {} } } diff --git a/src/risedevtool/src/service_config.rs b/src/risedevtool/src/service_config.rs index e5f149b8d10cd..fb95dbb520a0d 100644 --- a/src/risedevtool/src/service_config.rs +++ b/src/risedevtool/src/service_config.rs @@ -43,6 +43,17 @@ pub struct ComputeNodeConfig { pub role: String, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub enum MetaBackend { + Memory, + Etcd, + Sqlite, + Postgres, + Mysql, +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] @@ -60,8 +71,11 @@ pub struct MetaNodeConfig { pub user_managed: bool, + pub meta_backend: MetaBackend, pub provide_etcd_backend: Option>, pub provide_sqlite_backend: Option>, + pub provide_postgres_backend: Option>, + pub provide_mysql_backend: Option>, pub provide_prometheus: Option>, pub provide_compute_node: Option>, @@ -269,43 +283,55 @@ pub struct KafkaConfig { phantom_use: Option, pub id: String, + /// Advertise address pub address: String, #[serde(with = "string")] pub port: u16, - pub listen_address: String, + /// Port for other services in docker. They need to connect to `host.docker.internal`, while the host + /// need to connect to `localhost`. + pub docker_port: u16, + + #[serde(with = "string")] + pub controller_port: u16, - pub provide_zookeeper: Option>, + pub image: String, pub persist_data: bool, - pub broker_id: u32, + pub node_id: u32, pub user_managed: bool, } + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] -pub struct PubsubConfig { +pub struct SchemaRegistryConfig { #[serde(rename = "use")] phantom_use: Option, + pub id: String, + + pub address: String, #[serde(with = "string")] pub port: u16, - pub address: String, - pub persist_data: bool, + pub provide_kafka: Option>, + + pub image: String, + /// Redpanda supports schema registry natively. You can configure a `user_managed` schema registry + /// to use with redpanda. + pub user_managed: bool, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(deny_unknown_fields)] -pub struct ZooKeeperConfig { +pub struct PubsubConfig { #[serde(rename = "use")] phantom_use: Option, pub id: String, - - pub address: String, #[serde(with = "string")] pub port: u16, - pub listen_address: String, + pub address: String, pub persist_data: bool, } @@ -336,6 +362,56 @@ pub struct RedisConfig { pub address: String, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub enum Application { + Metastore, + Connector, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct MySqlConfig { + #[serde(rename = "use")] + phantom_use: Option, + pub id: String, + + pub port: u16, + pub address: String, + + pub user: String, + pub password: String, + pub database: String, + + pub application: Application, + pub image: String, + pub user_managed: bool, + pub persist_data: bool, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct PostgresConfig { + #[serde(rename = "use")] + phantom_use: Option, + pub id: String, + + pub port: u16, + pub address: String, + + pub user: String, + pub password: String, + pub database: String, + + pub application: Application, + pub image: String, + pub user_managed: bool, + pub persist_data: bool, +} + /// All service configuration #[derive(Clone, Debug, PartialEq)] pub enum ServiceConfig { @@ -352,10 +428,12 @@ pub enum ServiceConfig { Opendal(OpendalConfig), AwsS3(AwsS3Config), Kafka(KafkaConfig), + SchemaRegistry(SchemaRegistryConfig), Pubsub(PubsubConfig), Redis(RedisConfig), - ZooKeeper(ZooKeeperConfig), RedPanda(RedPandaConfig), + MySql(MySqlConfig), + Postgres(PostgresConfig), } impl ServiceConfig { @@ -372,15 +450,18 @@ impl ServiceConfig { Self::Grafana(c) => &c.id, Self::Tempo(c) => &c.id, Self::AwsS3(c) => &c.id, - Self::ZooKeeper(c) => &c.id, Self::Kafka(c) => &c.id, Self::Pubsub(c) => &c.id, Self::Redis(c) => &c.id, Self::RedPanda(c) => &c.id, Self::Opendal(c) => &c.id, + Self::MySql(c) => &c.id, + Self::Postgres(c) => &c.id, + Self::SchemaRegistry(c) => &c.id, } } + /// Used to check whether the port is occupied before running the service. pub fn port(&self) -> Option { match self { Self::ComputeNode(c) => Some(c.port), @@ -394,12 +475,14 @@ impl ServiceConfig { Self::Grafana(c) => Some(c.port), Self::Tempo(c) => Some(c.port), Self::AwsS3(_) => None, - Self::ZooKeeper(c) => Some(c.port), Self::Kafka(c) => Some(c.port), Self::Pubsub(c) => Some(c.port), Self::Redis(c) => Some(c.port), Self::RedPanda(_c) => None, Self::Opendal(_) => None, + Self::MySql(c) => Some(c.port), + Self::Postgres(c) => Some(c.port), + Self::SchemaRegistry(c) => Some(c.port), } } @@ -416,12 +499,14 @@ impl ServiceConfig { Self::Grafana(_c) => false, Self::Tempo(_c) => false, Self::AwsS3(_c) => false, - Self::ZooKeeper(_c) => false, Self::Kafka(c) => c.user_managed, Self::Pubsub(_c) => false, Self::Redis(_c) => false, Self::RedPanda(_c) => false, Self::Opendal(_c) => false, + Self::MySql(c) => c.user_managed, + Self::Postgres(c) => c.user_managed, + Self::SchemaRegistry(c) => c.user_managed, } } } diff --git a/src/risedevtool/src/task.rs b/src/risedevtool/src/task.rs index bec0df5ae812e..21b6f20eec5ee 100644 --- a/src/risedevtool/src/task.rs +++ b/src/risedevtool/src/task.rs @@ -15,6 +15,8 @@ mod compactor_service; mod compute_node_service; mod configure_tmux_service; +mod docker_service; +mod dummy_service; mod ensure_stop_service; mod etcd_service; mod frontend_service; @@ -22,21 +24,24 @@ mod grafana_service; mod kafka_service; mod meta_node_service; mod minio_service; +mod mysql_service; +mod postgres_service; mod prometheus_service; mod pubsub_service; mod redis_service; -mod task_configure_grpc_node; +mod schema_registry_service; mod task_configure_minio; mod task_etcd_ready_check; mod task_kafka_ready_check; +mod task_log_ready_check; mod task_pubsub_emu_ready_check; mod task_redis_ready_check; +mod task_tcp_ready_check; mod tempo_service; mod utils; -mod zookeeper_service; use std::env; -use std::net::TcpStream; +use std::net::{TcpStream, ToSocketAddrs}; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use std::sync::Arc; @@ -51,6 +56,7 @@ pub use utils::*; pub use self::compactor_service::*; pub use self::compute_node_service::*; pub use self::configure_tmux_service::*; +pub use self::dummy_service::DummyService; pub use self::ensure_stop_service::*; pub use self::etcd_service::*; pub use self::frontend_service::*; @@ -58,17 +64,20 @@ pub use self::grafana_service::*; pub use self::kafka_service::*; pub use self::meta_node_service::*; pub use self::minio_service::*; +pub use self::mysql_service::*; +pub use self::postgres_service::*; pub use self::prometheus_service::*; pub use self::pubsub_service::*; pub use self::redis_service::*; -pub use self::task_configure_grpc_node::*; +pub use self::schema_registry_service::SchemaRegistryService; pub use self::task_configure_minio::*; pub use self::task_etcd_ready_check::*; pub use self::task_kafka_ready_check::*; +pub use self::task_log_ready_check::*; pub use self::task_pubsub_emu_ready_check::*; pub use self::task_redis_ready_check::*; +pub use self::task_tcp_ready_check::*; pub use self::tempo_service::*; -pub use self::zookeeper_service::*; use crate::util::{complete_spin, get_program_args, get_program_name}; use crate::wait::{wait, wait_tcp_available}; @@ -104,6 +113,9 @@ where /// The status file corresponding to the current context. pub status_file: Option, + + /// The log file corresponding to the current context. (e.g. frontend-4566.log) + pub log_file: Option, } impl ExecuteContext @@ -116,6 +128,7 @@ where pb, status_dir, status_file: None, + log_file: None, id: None, } } @@ -124,8 +137,18 @@ where let id = task.id(); if !id.is_empty() { self.pb.set_prefix(id.clone()); - self.status_file = Some(self.status_dir.path().join(format!("{}.status", id))); - self.id = Some(id); + self.id = Some(id.clone()); + + // Remove the old status file if exists to avoid confusion. + let status_file = self.status_dir.path().join(format!("{}.status", id)); + fs_err::remove_file(&status_file).ok(); + self.status_file = Some(status_file); + + // Remove the old log file if exists to avoid confusion. + let log_file = Path::new(&env::var("PREFIX_LOG").unwrap()) + .join(format!("{}.log", self.id.as_ref().unwrap())); + fs_err::remove_file(&log_file).ok(); + self.log_file = Some(log_file); } } @@ -150,7 +173,7 @@ where writeln!(self.log, "---")?; - output.status.exit_ok()?; + output.status.exit_ok().context(full_output)?; Ok(output) } @@ -163,13 +186,16 @@ where self.status_file.clone().unwrap() } - pub fn log_path(&self) -> anyhow::Result { - let prefix_log = env::var("PREFIX_LOG")?; - Ok(Path::new(&prefix_log).join(format!("{}.log", self.id.as_ref().unwrap()))) + pub fn log_path(&self) -> &Path { + self.log_file.as_ref().unwrap().as_path() } pub fn wait_tcp(&mut self, server: impl AsRef) -> anyhow::Result<()> { - let addr = server.as_ref().parse()?; + let addr = server + .as_ref() + .to_socket_addrs()? + .next() + .with_context(|| format!("failed to resolve {}", server.as_ref()))?; wait( || { TcpStream::connect_timeout(&addr, Duration::from_secs(1)).with_context(|| { @@ -255,7 +281,11 @@ where /// Wait for a user-managed service to be available pub fn wait_tcp_user(&mut self, server: impl AsRef) -> anyhow::Result<()> { - let addr = server.as_ref().parse()?; + let addr = server + .as_ref() + .to_socket_addrs()? + .next() + .unwrap_or_else(|| panic!("failed to resolve {}", server.as_ref())); wait( || { TcpStream::connect_timeout(&addr, Duration::from_secs(1))?; @@ -294,7 +324,7 @@ where } } cmd.arg(Path::new(&prefix_path).join("run_command.sh")); - cmd.arg(self.log_path()?); + cmd.arg(self.log_path()); cmd.arg(self.status_path()); cmd.arg(user_cmd.get_program()); for arg in user_cmd.get_args() { diff --git a/src/risedevtool/src/task/compactor_service.rs b/src/risedevtool/src/task/compactor_service.rs index 1a2b24b36bc97..c79f4efd06ee5 100644 --- a/src/risedevtool/src/task/compactor_service.rs +++ b/src/risedevtool/src/task/compactor_service.rs @@ -76,7 +76,9 @@ impl Task for CompactorService { let mut cmd = self.compactor()?; - cmd.env("RUST_BACKTRACE", "1"); + if crate::util::is_enable_backtrace() { + cmd.env("RUST_BACKTRACE", "1"); + } if crate::util::is_env_set("RISEDEV_ENABLE_PROFILE") { cmd.env( diff --git a/src/risedevtool/src/task/compute_node_service.rs b/src/risedevtool/src/task/compute_node_service.rs index 3dfba5f3e3e16..95ff9d6412fa2 100644 --- a/src/risedevtool/src/task/compute_node_service.rs +++ b/src/risedevtool/src/task/compute_node_service.rs @@ -55,9 +55,9 @@ impl ComputeNodeService { .arg("--async-stack-trace") .arg(&config.async_stack_trace) .arg("--parallelism") - .arg(&config.parallelism.to_string()) + .arg(config.parallelism.to_string()) .arg("--total-memory-bytes") - .arg(&config.total_memory_bytes.to_string()) + .arg(config.total_memory_bytes.to_string()) .arg("--role") .arg(&config.role); @@ -80,10 +80,15 @@ impl Task for ComputeNodeService { let mut cmd = self.compute_node()?; - cmd.env("RUST_BACKTRACE", "1").env( + cmd.env( "TOKIO_CONSOLE_BIND", format!("127.0.0.1:{}", self.config.port + 1000), ); + + if crate::util::is_enable_backtrace() { + cmd.env("RUST_BACKTRACE", "1"); + } + if crate::util::is_env_set("RISEDEV_ENABLE_PROFILE") { cmd.env( "RW_PROFILE_PATH", diff --git a/src/risedevtool/src/task/docker_service.rs b/src/risedevtool/src/task/docker_service.rs new file mode 100644 index 0000000000000..b87ee8a6a8aef --- /dev/null +++ b/src/risedevtool/src/task/docker_service.rs @@ -0,0 +1,168 @@ +// Copyright 2024 RisingWave Labs +// +// 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::env; +use std::path::Path; +use std::process::{Command, Stdio}; + +use anyhow::{Context, Result}; + +use super::{ExecuteContext, Task}; +use crate::DummyService; + +/// Configuration for a docker-backed service. +/// +/// The trait can be implemented for the configuration struct of a service. +/// After that, use `DockerService` as the service type. +pub trait DockerServiceConfig: Send + 'static { + /// The unique identifier of the service. + fn id(&self) -> String; + + /// Whether the service is managed by the user. + /// + /// If true, no docker container will be started and the service will be forwarded to + /// the [`DummyService`]. + fn is_user_managed(&self) -> bool; + + /// The docker image to use, e.g. `mysql:5.7`. + fn image(&self) -> String; + + /// The environment variables to pass to the docker container. + fn envs(&self) -> Vec<(String, String)> { + vec![] + } + + /// The ports to expose on the host, e.g. `("0.0.0.0:23306", "3306")`. + /// + /// The first element of the tuple is the host port (or address), the second is the + /// container port. + fn ports(&self) -> Vec<(String, String)> { + vec![] + } + + /// The path in the container to persist data to, e.g. `/var/lib/mysql`. + /// + /// `Some` if the service is specified to persist data, `None` otherwise. + fn data_path(&self) -> Option { + None + } +} + +/// A service that runs a docker container with the given configuration. +pub struct DockerService { + config: B, +} + +impl DockerService +where + B: DockerServiceConfig, +{ + pub fn new(config: B) -> Self { + Self { config } + } + + /// Run `docker image inspect ` to check if the image exists locally. + /// + /// `docker run --pull=missing` does the same thing, but as we split the pull and run + /// into two commands while `pull` does not provide such an option, we need to check + /// the image existence manually. + fn check_image_exists(&self) -> bool { + Command::new("docker") + .arg("image") + .arg("inspect") + .arg(self.config.image()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|status| status.success()) + .unwrap_or(false) + } + + fn docker_pull(&self) -> Command { + let mut cmd = Command::new("docker"); + cmd.arg("pull").arg(self.config.image()); + cmd + } + + fn docker_run(&self) -> Result { + let mut cmd = Command::new("docker"); + cmd.arg("run") + .arg("--rm") + .arg("--name") + .arg(format!("risedev-{}", self.id())) + .arg("--add-host") + .arg("host.docker.internal:host-gateway"); + + for (k, v) in self.config.envs() { + cmd.arg("-e").arg(format!("{k}={v}")); + } + for (container, host) in self.config.ports() { + cmd.arg("-p").arg(format!("{container}:{host}")); + } + + if let Some(data_path) = self.config.data_path() { + let path = Path::new(&env::var("PREFIX_DATA")?).join(self.id()); + fs_err::create_dir_all(&path)?; + cmd.arg("-v") + .arg(format!("{}:{}", path.to_string_lossy(), data_path)); + } + + cmd.arg(self.config.image()); + + Ok(cmd) + } +} + +impl Task for DockerService +where + B: DockerServiceConfig, +{ + fn execute(&mut self, ctx: &mut ExecuteContext) -> anyhow::Result<()> { + if self.config.is_user_managed() { + return DummyService::new(&self.id()).execute(ctx); + } + + ctx.service(self); + + check_docker_installed()?; + + if !self.check_image_exists() { + ctx.pb + .set_message(format!("pulling image `{}`...", self.config.image())); + ctx.run_command(self.docker_pull())?; + } + + ctx.pb.set_message("starting..."); + ctx.run_command(ctx.tmux_run(self.docker_run()?)?)?; + + ctx.pb.set_message("started"); + + Ok(()) + } + + fn id(&self) -> String { + self.config.id() + } +} + +fn check_docker_installed() -> Result<()> { + Command::new("docker") + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map_err(anyhow::Error::from) + .and_then(|status| status.exit_ok().map_err(anyhow::Error::from)) + .context("service requires docker to be installed") +} diff --git a/java/udf/src/main/java/com/risingwave/functions/PeriodDuration.java b/src/risedevtool/src/task/dummy_service.rs similarity index 50% rename from java/udf/src/main/java/com/risingwave/functions/PeriodDuration.java rename to src/risedevtool/src/task/dummy_service.rs index 6d704100f6f35..f3d1054b1ca23 100644 --- a/java/udf/src/main/java/com/risingwave/functions/PeriodDuration.java +++ b/src/risedevtool/src/task/dummy_service.rs @@ -12,18 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.risingwave.functions; +use crate::{ExecuteContext, Task}; -import java.time.Duration; -import java.time.Period; +pub struct DummyService { + id: String, +} + +impl DummyService { + pub fn new(id: &str) -> Self { + Self { id: id.to_string() } + } +} -/** Combination of Period and Duration. */ -public class PeriodDuration extends org.apache.arrow.vector.PeriodDuration { - public PeriodDuration(Period period, Duration duration) { - super(period, duration); +impl Task for DummyService { + fn execute(&mut self, ctx: &mut ExecuteContext) -> anyhow::Result<()> { + ctx.service(self); + writeln!( + &mut ctx.log, + "{} is a dummy service. Please ensure it's correctly configured on your own! 🙏", + self.id + )?; + ctx.complete_spin(); + Ok(()) } - PeriodDuration(org.apache.arrow.vector.PeriodDuration base) { - super(base.getPeriod(), base.getDuration()); + fn id(&self) -> String { + self.id.clone() } } diff --git a/src/risedevtool/src/task/frontend_service.rs b/src/risedevtool/src/task/frontend_service.rs index 2ce67db6b7582..db944ad476278 100644 --- a/src/risedevtool/src/task/frontend_service.rs +++ b/src/risedevtool/src/task/frontend_service.rs @@ -87,7 +87,9 @@ impl Task for FrontendService { let mut cmd = self.frontend()?; - cmd.env("RUST_BACKTRACE", "1"); + if crate::util::is_enable_backtrace() { + cmd.env("RUST_BACKTRACE", "1"); + } let prefix_config = env::var("PREFIX_CONFIG")?; cmd.arg("--config-path") diff --git a/src/risedevtool/src/task/kafka_service.rs b/src/risedevtool/src/task/kafka_service.rs index df0eb4a0fa313..7c415b6d9749a 100644 --- a/src/risedevtool/src/task/kafka_service.rs +++ b/src/risedevtool/src/task/kafka_service.rs @@ -12,85 +12,71 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::env; -use std::path::{Path, PathBuf}; -use std::process::Command; +use super::docker_service::{DockerService, DockerServiceConfig}; +use crate::KafkaConfig; -use anyhow::{anyhow, Result}; - -use super::{ExecuteContext, Task}; -use crate::{KafkaConfig, KafkaGen}; - -pub struct KafkaService { - config: KafkaConfig, -} - -impl KafkaService { - pub fn new(config: KafkaConfig) -> Result { - Ok(Self { config }) +impl DockerServiceConfig for KafkaConfig { + fn id(&self) -> String { + self.id.clone() } - fn kafka_path(&self) -> Result { - let prefix_bin = env::var("PREFIX_BIN")?; - Ok(Path::new(&prefix_bin) - .join("kafka") - .join("bin") - .join("kafka-server-start.sh")) + fn is_user_managed(&self) -> bool { + self.user_managed } - fn kafka(&self) -> Result { - Ok(Command::new(self.kafka_path()?)) + fn image(&self) -> String { + self.image.clone() } -} - -impl Task for KafkaService { - fn execute(&mut self, ctx: &mut ExecuteContext) -> anyhow::Result<()> { - ctx.service(self); - ctx.pb.set_message("starting..."); - - let path = self.kafka_path()?; - if !path.exists() { - return Err(anyhow!("Kafka binary not found in {:?}\nDid you enable kafka feature in `./risedev configure`?", path)); - } - - let prefix_config = env::var("PREFIX_CONFIG")?; - let path = if self.config.persist_data { - Path::new(&env::var("PREFIX_DATA")?).join(self.id()) - } else { - let path = Path::new("/tmp/risedev").join(self.id()); - fs_err::remove_dir_all(&path).ok(); - path - }; - fs_err::create_dir_all(&path)?; - - let config_path = Path::new(&prefix_config).join(format!("{}.properties", self.id())); - fs_err::write( - &config_path, - KafkaGen.gen_server_properties(&self.config, &path.to_string_lossy()), - )?; - - let mut cmd = self.kafka()?; - - cmd.arg(config_path); - - if !self.config.user_managed { - ctx.run_command(ctx.tmux_run(cmd)?)?; - } else { - ctx.pb.set_message("user managed"); - writeln!( - &mut ctx.log, - "Please start your Kafka at {}:{}\n\n", - self.config.listen_address, self.config.port - )?; - } - - ctx.pb.set_message("started"); + fn envs(&self) -> Vec<(String, String)> { + vec![ + ("KAFKA_NODE_ID".to_owned(), self.node_id.to_string()), + ( + "KAFKA_PROCESS_ROLES".to_owned(), + "controller,broker".to_owned(), + ), + ( + "KAFKA_LISTENERS".to_owned(), + "HOST://:9092,CONTROLLER://:9093,DOCKER://:9094".to_owned(), + ), + ( + "KAFKA_ADVERTISED_LISTENERS".to_owned(), + format!( + "HOST://{}:{},DOCKER://host.docker.internal:{}", + self.address, self.port, self.docker_port + ), + ), + ( + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP".to_owned(), + "HOST:PLAINTEXT,CONTROLLER:PLAINTEXT,DOCKER:PLAINTEXT".to_owned(), + ), + ( + "KAFKA_CONTROLLER_QUORUM_VOTERS".to_owned(), + format!("{}@localhost:9093", self.node_id), + ), + ( + "KAFKA_CONTROLLER_LISTENER_NAMES".to_owned(), + "CONTROLLER".to_owned(), + ), + ( + "KAFKA_INTER_BROKER_LISTENER_NAME".to_owned(), + "HOST".to_owned(), + ), + ("CLUSTER_ID".to_owned(), "RiseDevRiseDevRiseDev1".to_owned()), + ] + } - Ok(()) + fn ports(&self) -> Vec<(String, String)> { + vec![ + (self.port.to_string(), "9092".to_owned()), + (self.docker_port.to_string(), "9094".to_owned()), + ] } - fn id(&self) -> String { - self.config.id.clone() + fn data_path(&self) -> Option { + self.persist_data.then(|| "/var/lib/kafka/data".to_owned()) } } + +/// Docker-backed Kafka service. +pub type KafkaService = DockerService; diff --git a/src/risedevtool/src/task/meta_node_service.rs b/src/risedevtool/src/task/meta_node_service.rs index 4a121bb630d3a..8dfebbdf4fa3c 100644 --- a/src/risedevtool/src/task/meta_node_service.rs +++ b/src/risedevtool/src/task/meta_node_service.rs @@ -21,7 +21,10 @@ use itertools::Itertools; use super::{ExecuteContext, Task}; use crate::util::{get_program_args, get_program_env_cmd, get_program_name}; -use crate::{add_hummock_backend, add_tempo_endpoint, HummockInMemoryStrategy, MetaNodeConfig}; +use crate::{ + add_hummock_backend, add_tempo_endpoint, Application, HummockInMemoryStrategy, MetaBackend, + MetaNodeConfig, +}; pub struct MetaNodeService { config: MetaNodeConfig, @@ -77,35 +80,84 @@ impl MetaNodeService { let mut is_persistent_meta_store = false; - if let Some(sqlite_config) = &config.provide_sqlite_backend - && !sqlite_config.is_empty() - { - is_persistent_meta_store = true; - let prefix_data = env::var("PREFIX_DATA")?; - let file_path = PathBuf::from(&prefix_data) - .join(&sqlite_config[0].id) - .join(&sqlite_config[0].file); - cmd.arg("--backend") - .arg("sql") - .arg("--sql-endpoint") - .arg(format!("sqlite://{}?mode=rwc", file_path.display())); - } else { - match config.provide_etcd_backend.as_ref().unwrap().as_slice() { - [] => { - cmd.arg("--backend").arg("mem"); - } - etcds => { - is_persistent_meta_store = true; - cmd.arg("--backend") - .arg("etcd") - .arg("--etcd-endpoints") - .arg( - etcds - .iter() - .map(|etcd| format!("{}:{}", etcd.address, etcd.port)) - .join(","), - ); - } + match &config.meta_backend { + MetaBackend::Memory => { + cmd.arg("--backend").arg("mem"); + } + MetaBackend::Etcd => { + let etcd_config = config.provide_etcd_backend.as_ref().unwrap(); + assert!(!etcd_config.is_empty()); + is_persistent_meta_store = true; + + cmd.arg("--backend") + .arg("etcd") + .arg("--etcd-endpoints") + .arg( + etcd_config + .iter() + .map(|etcd| format!("{}:{}", etcd.address, etcd.port)) + .join(","), + ); + } + MetaBackend::Sqlite => { + let sqlite_config = config.provide_sqlite_backend.as_ref().unwrap(); + assert_eq!(sqlite_config.len(), 1); + is_persistent_meta_store = true; + + let prefix_data = env::var("PREFIX_DATA")?; + let file_path = PathBuf::from(&prefix_data) + .join(&sqlite_config[0].id) + .join(&sqlite_config[0].file); + cmd.arg("--backend") + .arg("sqlite") + .arg("--sql-endpoint") + .arg(file_path); + } + MetaBackend::Postgres => { + let pg_config = config.provide_postgres_backend.as_ref().unwrap(); + let pg_store_config = pg_config + .iter() + .filter(|c| c.application == Application::Metastore) + .exactly_one() + .expect("more than one or no pg store config found for metastore"); + is_persistent_meta_store = true; + + cmd.arg("--backend") + .arg("postgres") + .arg("--sql-endpoint") + .arg(format!( + "{}:{}", + pg_store_config.address, pg_store_config.port, + )) + .arg("--sql-username") + .arg(&pg_store_config.user) + .arg("--sql-password") + .arg(&pg_store_config.password) + .arg("--sql-database") + .arg(&pg_store_config.database); + } + MetaBackend::Mysql => { + let mysql_config = config.provide_mysql_backend.as_ref().unwrap(); + let mysql_store_config = mysql_config + .iter() + .filter(|c| c.application == Application::Metastore) + .exactly_one() + .expect("more than one or no mysql store config found for metastore"); + is_persistent_meta_store = true; + + cmd.arg("--backend") + .arg("mysql") + .arg("--sql-endpoint") + .arg(format!( + "{}:{}", + mysql_store_config.address, mysql_store_config.port, + )) + .arg("--sql-username") + .arg(&mysql_store_config.user) + .arg("--sql-password") + .arg(&mysql_store_config.password) + .arg("--sql-database") + .arg(&mysql_store_config.database); } } @@ -181,7 +233,9 @@ impl Task for MetaNodeService { let mut cmd = self.meta_node()?; - cmd.env("RUST_BACKTRACE", "1"); + if crate::util::is_enable_backtrace() { + cmd.env("RUST_BACKTRACE", "1"); + } if crate::util::is_env_set("RISEDEV_ENABLE_PROFILE") { cmd.env( diff --git a/src/risedevtool/src/task/mysql_service.rs b/src/risedevtool/src/task/mysql_service.rs new file mode 100644 index 0000000000000..06fbb1a62bb08 --- /dev/null +++ b/src/risedevtool/src/task/mysql_service.rs @@ -0,0 +1,59 @@ +// Copyright 2024 RisingWave Labs +// +// 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 super::docker_service::{DockerService, DockerServiceConfig}; +use crate::MySqlConfig; + +impl DockerServiceConfig for MySqlConfig { + fn id(&self) -> String { + self.id.clone() + } + + fn is_user_managed(&self) -> bool { + self.user_managed + } + + fn image(&self) -> String { + self.image.clone() + } + + fn envs(&self) -> Vec<(String, String)> { + let mut envs = vec![("MYSQL_DATABASE".to_owned(), self.database.clone())]; + if self.user == "root" { + if self.password.is_empty() { + envs.push(("MYSQL_ALLOW_EMPTY_PASSWORD".to_owned(), "1".to_owned())); + } else { + envs.push(("MYSQL_ROOT_PASSWORD".to_owned(), self.password.clone())); + } + } else { + envs.extend([ + ("MYSQL_ALLOW_EMPTY_PASSWORD".to_owned(), "1".to_owned()), + ("MYSQL_USER".to_owned(), self.user.clone()), + ("MYSQL_PASSWORD".to_owned(), self.password.clone()), + ]); + } + envs + } + + fn ports(&self) -> Vec<(String, String)> { + vec![(self.port.to_string(), "3306".to_owned())] + } + + fn data_path(&self) -> Option { + self.persist_data.then(|| "/var/lib/mysql".to_owned()) + } +} + +/// Docker-backed MySQL service. +pub type MySqlService = DockerService; diff --git a/src/risedevtool/src/task/postgres_service.rs b/src/risedevtool/src/task/postgres_service.rs new file mode 100644 index 0000000000000..45700c93a1a97 --- /dev/null +++ b/src/risedevtool/src/task/postgres_service.rs @@ -0,0 +1,55 @@ +// Copyright 2024 RisingWave Labs +// +// 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 crate::task::docker_service::{DockerService, DockerServiceConfig}; +use crate::PostgresConfig; + +impl DockerServiceConfig for PostgresConfig { + fn id(&self) -> String { + self.id.clone() + } + + fn is_user_managed(&self) -> bool { + self.user_managed + } + + fn image(&self) -> String { + self.image.clone() + } + + fn envs(&self) -> Vec<(String, String)> { + vec![ + ("POSTGRES_HOST_AUTH_METHOD".to_owned(), "trust".to_owned()), + ("POSTGRES_USER".to_owned(), self.user.clone()), + ("POSTGRES_PASSWORD".to_owned(), self.password.clone()), + ("POSTGRES_DB".to_owned(), self.database.clone()), + ( + "POSTGRES_INITDB_ARGS".to_owned(), + "--encoding=UTF-8 --lc-collate=C --lc-ctype=C".to_owned(), + ), + ] + } + + fn ports(&self) -> Vec<(String, String)> { + vec![(self.port.to_string(), "5432".to_owned())] + } + + fn data_path(&self) -> Option { + self.persist_data + .then(|| "/var/lib/postgresql/data".to_owned()) + } +} + +/// Docker-backed PostgreSQL service. +pub type PostgresService = DockerService; diff --git a/src/risedevtool/src/task/schema_registry_service.rs b/src/risedevtool/src/task/schema_registry_service.rs new file mode 100644 index 0000000000000..5c5eba4fa8f35 --- /dev/null +++ b/src/risedevtool/src/task/schema_registry_service.rs @@ -0,0 +1,65 @@ +// Copyright 2024 RisingWave Labs +// +// 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 super::docker_service::{DockerService, DockerServiceConfig}; +use crate::SchemaRegistryConfig; + +impl DockerServiceConfig for SchemaRegistryConfig { + fn id(&self) -> String { + self.id.clone() + } + + fn is_user_managed(&self) -> bool { + self.user_managed + } + + fn image(&self) -> String { + self.image.clone() + } + + fn envs(&self) -> Vec<(String, String)> { + // https://docs.confluent.io/platform/current/installation/docker/config-reference.html#sr-long-configuration + // https://docs.confluent.io/platform/current/schema-registry/installation/config.html + let kafka = self + .provide_kafka + .as_ref() + .expect("Kafka is required for Schema Registry"); + if kafka.len() != 1 { + panic!("More than one Kafka is not supported yet"); + } + let kafka = &kafka[0]; + vec![ + ("SCHEMA_REGISTRY_HOST_NAME".to_owned(), self.address.clone()), + ( + "SCHEMA_REGISTRY_LISTENERS".to_owned(), + format!("http://{}:{}", self.address, self.port), + ), + ( + "SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS".to_owned(), + format!("host.docker.internal:{}", kafka.docker_port), + ), + ] + } + + fn ports(&self) -> Vec<(String, String)> { + vec![(self.port.to_string(), "8081".to_owned())] + } + + fn data_path(&self) -> Option { + None + } +} + +/// Docker-backed Schema Registry service. +pub type SchemaRegistryService = DockerService; diff --git a/src/risedevtool/src/task/task_log_ready_check.rs b/src/risedevtool/src/task/task_log_ready_check.rs new file mode 100644 index 0000000000000..a81d6961e491c --- /dev/null +++ b/src/risedevtool/src/task/task_log_ready_check.rs @@ -0,0 +1,86 @@ +// Copyright 2024 RisingWave Labs +// +// 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::io::{Read as _, Seek as _, SeekFrom}; +use std::time::Duration; + +use anyhow::{bail, Context, Result}; +use fs_err::File; + +use super::{ExecuteContext, Task}; +use crate::wait::wait; + +/// Check if a log pattern is found in the log output indicating the service is ready. +pub struct LogReadyCheckTask { + pattern: String, +} + +impl LogReadyCheckTask { + pub fn new(pattern: impl Into) -> Result { + Ok(Self { + pattern: pattern.into(), + }) + } +} + +impl Task for LogReadyCheckTask { + fn execute(&mut self, ctx: &mut ExecuteContext) -> anyhow::Result<()> { + let Some(id) = ctx.id.clone() else { + panic!("Service should be set before executing LogReadyCheckTask"); + }; + + ctx.pb.set_message("waiting for ready..."); + ctx.wait_log_contains(&self.pattern) + .with_context(|| format!("failed to wait for service `{id}` to be ready"))?; + + ctx.complete_spin(); + + Ok(()) + } +} + +impl ExecuteContext +where + W: std::io::Write, +{ + fn wait_log_contains(&mut self, pattern: impl AsRef) -> anyhow::Result<()> { + let pattern = pattern.as_ref(); + let log_path = self.log_path().to_path_buf(); + + let mut content = String::new(); + let mut offset = 0; + + wait( + || { + let mut file = File::open(&log_path).context("log file does not exist")?; + file.seek(SeekFrom::Start(offset as u64))?; + offset += file.read_to_string(&mut content)?; + + // Always going through the whole log file could be stupid, but it's reliable. + if content.contains(pattern) { + Ok(()) + } else { + bail!("pattern \"{}\" not found in log", pattern) + } + }, + &mut self.log, + self.status_file.as_ref().unwrap(), + self.id.as_ref().unwrap(), + Some(Duration::from_secs(30)), + true, + )?; + + Ok(()) + } +} diff --git a/src/risedevtool/src/task/task_configure_grpc_node.rs b/src/risedevtool/src/task/task_tcp_ready_check.rs similarity index 67% rename from src/risedevtool/src/task/task_configure_grpc_node.rs rename to src/risedevtool/src/task/task_tcp_ready_check.rs index 75a69a7cdb29f..98b4238ca8830 100644 --- a/src/risedevtool/src/task/task_configure_grpc_node.rs +++ b/src/risedevtool/src/task/task_tcp_ready_check.rs @@ -12,17 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use anyhow::Result; +use anyhow::{Context, Result}; use super::{ExecuteContext, Task}; -pub struct ConfigureGrpcNodeTask { +/// Check if a TCP port can be connected to. +/// +/// Note that accepting a connection does not always mean the service is ready. +pub struct TcpReadyCheckTask { advertise_address: String, port: u16, user_managed: bool, } -impl ConfigureGrpcNodeTask { +impl TcpReadyCheckTask { pub fn new(advertise_address: String, port: u16, user_managed: bool) -> Result { Ok(Self { advertise_address, @@ -32,18 +35,24 @@ impl ConfigureGrpcNodeTask { } } -impl Task for ConfigureGrpcNodeTask { +impl Task for TcpReadyCheckTask { fn execute(&mut self, ctx: &mut ExecuteContext) -> anyhow::Result<()> { + let Some(id) = ctx.id.clone() else { + panic!("Service should be set before executing TcpReadyCheckTask"); + }; let address = format!("{}:{}", self.advertise_address, self.port); if self.user_managed { ctx.pb.set_message( "waiting for user-managed service online... (see `risedev.log` for cli args)", ); - ctx.wait_tcp_user(&address)?; + ctx.wait_tcp_user(&address).with_context(|| { + format!("failed to wait for user-managed service `{id}` to be online") + })?; } else { ctx.pb.set_message("waiting for online..."); - ctx.wait_tcp(&address)?; + ctx.wait_tcp(&address) + .with_context(|| format!("failed to wait for service `{id}` to be online"))?; } ctx.complete_spin(); diff --git a/src/risedevtool/src/task/zookeeper_service.rs b/src/risedevtool/src/task/zookeeper_service.rs deleted file mode 100644 index 5a94de095cab9..0000000000000 --- a/src/risedevtool/src/task/zookeeper_service.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// 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::env; -use std::path::{Path, PathBuf}; -use std::process::Command; - -use anyhow::{anyhow, Result}; - -use super::{ExecuteContext, Task}; -use crate::{ZooKeeperConfig, ZooKeeperGen}; - -pub struct ZooKeeperService { - config: ZooKeeperConfig, -} - -impl ZooKeeperService { - pub fn new(config: ZooKeeperConfig) -> Result { - Ok(Self { config }) - } - - fn zookeeper_path(&self) -> Result { - let prefix_bin = env::var("PREFIX_BIN")?; - Ok(Path::new(&prefix_bin) - .join("kafka") - .join("bin") - .join("zookeeper-server-start.sh")) - } - - fn zookeeper(&self) -> Result { - Ok(Command::new(self.zookeeper_path()?)) - } -} - -impl Task for ZooKeeperService { - fn execute(&mut self, ctx: &mut ExecuteContext) -> anyhow::Result<()> { - ctx.service(self); - ctx.pb.set_message("starting..."); - - let path = self.zookeeper_path()?; - if !path.exists() { - return Err(anyhow!("ZooKeeper binary not found in {:?}\nDid you enable kafka feature in `./risedev configure`?", path)); - } - - let prefix_config = env::var("PREFIX_CONFIG")?; - - let path = if self.config.persist_data { - Path::new(&env::var("PREFIX_DATA")?).join(self.id()) - } else { - let path = Path::new("/tmp/risedev").join(self.id()); - fs_err::remove_dir_all(&path).ok(); - path - }; - fs_err::create_dir_all(&path)?; - - let config_path = Path::new(&prefix_config).join(format!("{}.properties", self.id())); - fs_err::write( - &config_path, - ZooKeeperGen.gen_server_properties(&self.config, &path.to_string_lossy()), - )?; - - let mut cmd = self.zookeeper()?; - - cmd.arg(config_path); - - ctx.run_command(ctx.tmux_run(cmd)?)?; - - ctx.pb.set_message("started"); - - Ok(()) - } - - fn id(&self) -> String { - self.config.id.clone() - } -} diff --git a/src/risedevtool/src/util.rs b/src/risedevtool/src/util.rs index a0cd7153515c5..42aa85730aa6c 100644 --- a/src/risedevtool/src/util.rs +++ b/src/risedevtool/src/util.rs @@ -79,3 +79,7 @@ pub fn is_env_set(var: &str) -> bool { } false } + +pub fn is_enable_backtrace() -> bool { + !is_env_set("DISABLE_BACKTRACE") +} diff --git a/src/risedevtool/src/wait.rs b/src/risedevtool/src/wait.rs index ddedeb547270b..f2530371b83f8 100644 --- a/src/risedevtool/src/wait.rs +++ b/src/risedevtool/src/wait.rs @@ -13,12 +13,12 @@ // limitations under the License. use std::io::Read; -use std::net::TcpStream; +use std::net::{TcpStream, ToSocketAddrs}; use std::path::Path; use std::thread::sleep; use std::time::Duration; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context as _, Result}; use console::style; pub fn wait( @@ -75,7 +75,7 @@ pub fn wait( }); } - sleep(Duration::from_millis(30)); + sleep(Duration::from_millis(100)); } } @@ -84,7 +84,10 @@ pub fn wait_tcp_available( timeout: Option, ) -> anyhow::Result<()> { let server = server.as_ref(); - let addr = server.parse()?; + let addr = server + .to_socket_addrs()? + .next() + .with_context(|| format!("failed to resolve {}", server))?; let start_time = std::time::Instant::now(); loop { @@ -101,6 +104,6 @@ pub fn wait_tcp_available( } } - sleep(Duration::from_millis(50)); + sleep(Duration::from_millis(100)); } } diff --git a/src/rpc_client/Cargo.toml b/src/rpc_client/Cargo.toml index c6e31e8eb0d76..70bebfa154fc4 100644 --- a/src/rpc_client/Cargo.toml +++ b/src/rpc_client/Cargo.toml @@ -17,7 +17,7 @@ normal = ["workspace-hack"] anyhow = "1" async-trait = "0.1" easy-ext = "1" -either = "1.11.0" +either = "1.12.0" futures = { version = "0.3", default-features = false, features = ["alloc"] } http = "0.2" hyper = "0.14" # required by tonic diff --git a/src/rpc_client/src/connector_client.rs b/src/rpc_client/src/connector_client.rs index 896f198b8a5f1..30d78290b6d98 100644 --- a/src/rpc_client/src/connector_client.rs +++ b/src/rpc_client/src/connector_client.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::HashMap; +use std::collections::BTreeMap; use std::fmt::Debug; use std::time::Duration; @@ -30,6 +30,7 @@ use risingwave_pb::connector_service::sink_writer_stream_request::{ }; use risingwave_pb::connector_service::sink_writer_stream_response::CommitResponse; use risingwave_pb::connector_service::*; +use risingwave_pb::plan_common::column_desc::GeneratedOrDefaultColumn; use thiserror_ext::AsReport; use tokio_stream::wrappers::ReceiverStream; use tonic::transport::{Channel, Endpoint}; @@ -201,7 +202,7 @@ impl ConnectorClient { source_id: u64, source_type: SourceType, start_offset: Option, - properties: HashMap, + properties: BTreeMap, snapshot_done: bool, is_source_job: bool, ) -> Result> { @@ -233,11 +234,20 @@ impl ConnectorClient { &self, source_id: u64, source_type: SourceType, - properties: HashMap, + properties: BTreeMap, table_schema: Option, is_source_job: bool, is_backfill_table: bool, ) -> Result<()> { + let table_schema = table_schema.map(|mut table_schema| { + table_schema.columns.retain(|c| { + !matches!( + c.generated_or_default_column, + Some(GeneratedOrDefaultColumn::GeneratedColumn(_)) + ) + }); + table_schema + }); let response = self .rpc_client .clone() @@ -268,7 +278,6 @@ impl ConnectorClient { &self, payload_schema: Option, sink_proto: PbSinkParam, - sink_payload_format: SinkPayloadFormat, ) -> Result { let mut rpc_client = self.rpc_client.clone(); let (handle, first_rsp) = SinkWriterStreamHandle::initialize( @@ -276,7 +285,6 @@ impl ConnectorClient { request: Some(SinkRequest::Start(StartSink { payload_schema, sink_param: Some(sink_proto), - format: sink_payload_format as i32, })), }, |rx| async move { diff --git a/src/rpc_client/src/meta_client.rs b/src/rpc_client/src/meta_client.rs index fbb8dff1f5a98..cd295abc9ba39 100644 --- a/src/rpc_client/src/meta_client.rs +++ b/src/rpc_client/src/meta_client.rs @@ -23,9 +23,9 @@ use async_trait::async_trait; use either::Either; use futures::stream::BoxStream; use lru::LruCache; -use risingwave_common::catalog::{CatalogVersion, FunctionId, IndexId, TableId}; +use risingwave_common::catalog::{CatalogVersion, FunctionId, IndexId, SecretId, TableId}; use risingwave_common::config::{MetaConfig, MAX_CONNECTION_WINDOW_SIZE}; -use risingwave_common::hash::ParallelUnitMapping; +use risingwave_common::hash::WorkerSlotMapping; use risingwave_common::system_param::reader::SystemParamsReader; use risingwave_common::telemetry::report::TelemetryInfoFetcher; use risingwave_common::util::addr::HostAddr; @@ -65,7 +65,6 @@ use risingwave_pb::meta::add_worker_node_request::Property; use risingwave_pb::meta::cancel_creating_jobs_request::PbJobs; use risingwave_pb::meta::cluster_service_client::ClusterServiceClient; use risingwave_pb::meta::event_log_service_client::EventLogServiceClient; -use risingwave_pb::meta::get_reschedule_plan_request::PbPolicy; use risingwave_pb::meta::heartbeat_request::{extra_info, ExtraInfo}; use risingwave_pb::meta::heartbeat_service_client::HeartbeatServiceClient; use risingwave_pb::meta::list_actor_states_response::ActorState; @@ -172,6 +171,25 @@ impl MetaClient { Ok(resp.version) } + pub async fn create_secret( + &self, + secret_name: String, + database_id: u32, + schema_id: u32, + owner_id: u32, + value: Vec, + ) -> Result { + let request = CreateSecretRequest { + name: secret_name, + database_id, + schema_id, + owner_id, + value, + }; + let resp = self.inner.create_secret(request).await?; + Ok(resp.version) + } + pub async fn list_connections(&self, _name: Option<&str>) -> Result> { let request = ListConnectionsRequest {}; let resp = self.inner.list_connections(request).await?; @@ -184,6 +202,14 @@ impl MetaClient { Ok(resp.version) } + pub async fn drop_secret(&self, secret_id: SecretId) -> Result { + let request = DropSecretRequest { + secret_id: secret_id.into(), + }; + let resp = self.inner.drop_secret(request).await?; + Ok(resp.version) + } + /// Register the current node to the cluster and set the corresponding worker id. pub async fn register_new( addr_strategy: MetaAddressStrategy, @@ -384,11 +410,9 @@ impl MetaClient { pub async fn create_subscription( &self, subscription: PbSubscription, - graph: StreamFragmentGraph, ) -> Result { let request = CreateSubscriptionRequest { subscription: Some(subscription), - fragment_graph: Some(graph), }; let resp = self.inner.create_subscription(request).await?; @@ -503,6 +527,7 @@ impl MetaClient { table: PbTable, graph: StreamFragmentGraph, table_col_index_mapping: ColIndexMapping, + job_type: PbTableJobType, ) -> Result { let request = ReplaceTablePlanRequest { plan: Some(ReplaceTablePlan { @@ -510,6 +535,7 @@ impl MetaClient { table: Some(table), fragment_graph: Some(graph), table_col_index_mapping: Some(table_col_index_mapping.to_protobuf()), + job_type: job_type as _, }), }; let resp = self.inner.replace_table_plan(request).await?; @@ -596,6 +622,21 @@ impl MetaClient { Ok(resp.version) } + pub async fn list_change_log_epochs( + &self, + table_id: u32, + min_epoch: u64, + max_count: u32, + ) -> Result> { + let request = ListChangeLogEpochsRequest { + table_id, + min_epoch, + max_count, + }; + let resp = self.inner.list_change_log_epochs(request).await?; + Ok(resp.epochs) + } + pub async fn drop_index(&self, index_id: IndexId, cascade: bool) -> Result { let request = DropIndexRequest { index_id: index_id.index_id, @@ -692,15 +733,34 @@ impl MetaClient { Ok(resp.version) } - /// Unregister the current node to the cluster. - pub async fn unregister(&self, addr: HostAddr) -> Result<()> { + /// Unregister the current node from the cluster. + pub async fn unregister(&self) -> Result<()> { let request = DeleteWorkerNodeRequest { - host: Some(addr.to_protobuf()), + host: Some(self.host_addr.to_protobuf()), }; self.inner.delete_worker_node(request).await?; Ok(()) } + /// Try to unregister the current worker from the cluster with best effort. Log the result. + pub async fn try_unregister(&self) { + match self.unregister().await { + Ok(_) => { + tracing::info!( + worker_id = self.worker_id(), + "successfully unregistered from meta service", + ) + } + Err(e) => { + tracing::warn!( + error = %e.as_report(), + worker_id = self.worker_id(), + "failed to unregister from meta service", + ); + } + } + } + pub async fn update_schedulability( &self, worker_ids: &[u32], @@ -901,19 +961,6 @@ impl MetaClient { Ok((resp.success, resp.revision)) } - pub async fn get_reschedule_plan( - &self, - policy: PbPolicy, - revision: u64, - ) -> Result { - let request = GetReschedulePlanRequest { - revision, - policy: Some(policy), - }; - let resp = self.inner.get_reschedule_plan(request).await?; - Ok(resp) - } - pub async fn risectl_get_pinned_versions_summary( &self, ) -> Result { @@ -1158,11 +1205,11 @@ impl MetaClient { pub async fn list_serving_vnode_mappings( &self, - ) -> Result> { + ) -> Result> { let req = GetServingVnodeMappingsRequest {}; let resp = self.inner.get_serving_vnode_mappings(req).await?; let mappings = resp - .mappings + .worker_slot_mappings .into_iter() .map(|p| { ( @@ -1172,7 +1219,7 @@ impl MetaClient { .get(&p.fragment_id) .cloned() .unwrap_or(0), - ParallelUnitMapping::from_protobuf(p.mapping.as_ref().unwrap()), + WorkerSlotMapping::from_protobuf(p.mapping.as_ref().unwrap()), ), ) }) @@ -1918,12 +1965,14 @@ macro_rules! for_all_meta_rpc { ,{ ddl_client, create_subscription, CreateSubscriptionRequest, CreateSubscriptionResponse } ,{ ddl_client, create_schema, CreateSchemaRequest, CreateSchemaResponse } ,{ ddl_client, create_database, CreateDatabaseRequest, CreateDatabaseResponse } + ,{ ddl_client, create_secret, CreateSecretRequest, CreateSecretResponse } ,{ ddl_client, create_index, CreateIndexRequest, CreateIndexResponse } ,{ ddl_client, create_function, CreateFunctionRequest, CreateFunctionResponse } ,{ ddl_client, drop_table, DropTableRequest, DropTableResponse } ,{ ddl_client, drop_materialized_view, DropMaterializedViewRequest, DropMaterializedViewResponse } ,{ ddl_client, drop_view, DropViewRequest, DropViewResponse } ,{ ddl_client, drop_source, DropSourceRequest, DropSourceResponse } + , {ddl_client, drop_secret, DropSecretRequest, DropSecretResponse} ,{ ddl_client, drop_sink, DropSinkRequest, DropSinkResponse } ,{ ddl_client, drop_subscription, DropSubscriptionRequest, DropSubscriptionResponse } ,{ ddl_client, drop_database, DropDatabaseRequest, DropDatabaseResponse } @@ -1976,6 +2025,7 @@ macro_rules! for_all_meta_rpc { ,{ hummock_client, list_compact_task_assignment, ListCompactTaskAssignmentRequest, ListCompactTaskAssignmentResponse } ,{ hummock_client, list_compact_task_progress, ListCompactTaskProgressRequest, ListCompactTaskProgressResponse } ,{ hummock_client, cancel_compact_task, CancelCompactTaskRequest, CancelCompactTaskResponse} + ,{ hummock_client, list_change_log_epochs, ListChangeLogEpochsRequest, ListChangeLogEpochsResponse } ,{ user_client, create_user, CreateUserRequest, CreateUserResponse } ,{ user_client, update_user, UpdateUserRequest, UpdateUserResponse } ,{ user_client, drop_user, DropUserRequest, DropUserResponse } @@ -1983,7 +2033,6 @@ macro_rules! for_all_meta_rpc { ,{ user_client, revoke_privilege, RevokePrivilegeRequest, RevokePrivilegeResponse } ,{ scale_client, get_cluster_info, GetClusterInfoRequest, GetClusterInfoResponse } ,{ scale_client, reschedule, RescheduleRequest, RescheduleResponse } - ,{ scale_client, get_reschedule_plan, GetReschedulePlanRequest, GetReschedulePlanResponse } ,{ notification_client, subscribe, SubscribeRequest, Streaming } ,{ backup_client, backup_meta, BackupMetaRequest, BackupMetaResponse } ,{ backup_client, get_backup_job_status, GetBackupJobStatusRequest, GetBackupJobStatusResponse } diff --git a/src/rpc_client/src/sink_coordinate_client.rs b/src/rpc_client/src/sink_coordinate_client.rs index 74c05fa85de8e..06602ef4db3b7 100644 --- a/src/rpc_client/src/sink_coordinate_client.rs +++ b/src/rpc_client/src/sink_coordinate_client.rs @@ -16,7 +16,7 @@ use std::future::Future; use anyhow::anyhow; use futures::{Stream, TryStreamExt}; -use risingwave_common::buffer::Bitmap; +use risingwave_common::bitmap::Bitmap; use risingwave_pb::connector_service::coordinate_request::{ CommitRequest, StartCoordinationRequest, }; diff --git a/src/sqlparser/Cargo.toml b/src/sqlparser/Cargo.toml index ef57da9aa62f8..bf6307e3ace68 100644 --- a/src/sqlparser/Cargo.toml +++ b/src/sqlparser/Cargo.toml @@ -1,10 +1,7 @@ [package] name = "risingwave_sqlparser" license = "Apache-2.0" -include = [ - "src/**/*.rs", - "Cargo.toml", -] +include = ["src/**/*.rs", "Cargo.toml"] version = { workspace = true } edition = { workspace = true } homepage = { workspace = true } @@ -27,14 +24,28 @@ normal = ["workspace-hack"] [dependencies] itertools = { workspace = true } serde = { version = "1.0", features = ["derive"], optional = true } +thiserror = "1.0.61" +tokio = { version = "0.2", package = "madsim-tokio", features = [ + "rt", + "rt-multi-thread", + "macros", +] } tracing = "0.1" tracing-subscriber = "0.3" +winnow = "0.6.11" [target.'cfg(not(madsim))'.dependencies] workspace-hack = { path = "../workspace-hack" } [dev-dependencies] +anyhow = "1" +console = "0.15" +libtest-mimic = "0.7" matches = "0.1" +serde = { version = "1", features = ["derive"] } +serde_with = "3" +serde_yaml = "0.9" +walkdir = "2" [package.metadata.release] # Instruct `cargo release` to not run `cargo publish` locally: @@ -46,5 +57,9 @@ disable-publish = true name = "sqlparser" path = "src/bin/sqlparser.rs" +[[test]] +name = "parser_test" +harness = false + [lints] workspace = true diff --git a/src/sqlparser/README.md b/src/sqlparser/README.md index 2a829102710ba..e4e90a06eab84 100644 --- a/src/sqlparser/README.md +++ b/src/sqlparser/README.md @@ -5,4 +5,4 @@ This parser is a fork of . ## Add a new test case 1. Copy an item in the yaml file and edit the `input` to the sql you want to test. -2. Run `./risedev do-apply-parser-test` to regenerate the `formatted_sql` which is the expected output. \ No newline at end of file +2. Run `./risedev update-parser-test` to regenerate the `formatted_sql` which is the expected output. \ No newline at end of file diff --git a/src/sqlparser/sqlparser_test.toml b/src/sqlparser/sqlparser_test.toml new file mode 100644 index 0000000000000..8e348e8db925b --- /dev/null +++ b/src/sqlparser/sqlparser_test.toml @@ -0,0 +1,8 @@ +[tasks.update-parser-test] +description = "Update parser test data" +private = true +script = ''' +#!/usr/bin/env bash +set -e +UPDATE_PARSER_TEST=1 cargo test --test parser_test +''' diff --git a/src/sqlparser/src/ast/ddl.rs b/src/sqlparser/src/ast/ddl.rs index a166182ac78e1..92683f42e742a 100644 --- a/src/sqlparser/src/ast/ddl.rs +++ b/src/sqlparser/src/ast/ddl.rs @@ -28,7 +28,6 @@ use crate::tokenizer::Token; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterDatabaseOperation { ChangeOwner { new_owner_name: Ident }, RenameDatabase { database_name: ObjectName }, @@ -36,7 +35,6 @@ pub enum AlterDatabaseOperation { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterSchemaOperation { ChangeOwner { new_owner_name: Ident }, RenameSchema { schema_name: ObjectName }, @@ -104,11 +102,14 @@ pub enum AlterTableOperation { deferred: bool, }, RefreshSchema, + /// `SET STREAMING_RATE_LIMIT TO ` + SetStreamingRateLimit { + rate_limit: i32, + }, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterIndexOperation { RenameIndex { index_name: ObjectName, @@ -122,7 +123,6 @@ pub enum AlterIndexOperation { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterViewOperation { RenameView { view_name: ObjectName, @@ -138,11 +138,14 @@ pub enum AlterViewOperation { parallelism: SetVariableValue, deferred: bool, }, + /// `SET STREAMING_RATE_LIMIT TO ` + SetStreamingRateLimit { + rate_limit: i32, + }, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterSinkOperation { RenameSink { sink_name: ObjectName, @@ -162,27 +165,14 @@ pub enum AlterSinkOperation { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterSubscriptionOperation { - RenameSubscription { - subscription_name: ObjectName, - }, - ChangeOwner { - new_owner_name: Ident, - }, - SetSchema { - new_schema_name: ObjectName, - }, - /// `SET PARALLELISM TO [ DEFERRED ]` - SetParallelism { - parallelism: SetVariableValue, - deferred: bool, - }, + RenameSubscription { subscription_name: ObjectName }, + ChangeOwner { new_owner_name: Ident }, + SetSchema { new_schema_name: ObjectName }, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterSourceOperation { RenameSource { source_name: ObjectName }, AddColumn { column_def: ColumnDef }, @@ -190,18 +180,17 @@ pub enum AlterSourceOperation { SetSchema { new_schema_name: ObjectName }, FormatEncode { connector_schema: ConnectorSchema }, RefreshSchema, + SetStreamingRateLimit { rate_limit: i32 }, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterFunctionOperation { SetSchema { new_schema_name: ObjectName }, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterConnectionOperation { SetSchema { new_schema_name: ObjectName }, } @@ -301,6 +290,9 @@ impl fmt::Display for AlterTableOperation { AlterTableOperation::RefreshSchema => { write!(f, "REFRESH SCHEMA") } + AlterTableOperation::SetStreamingRateLimit { rate_limit } => { + write!(f, "SET STREAMING_RATE_LIMIT TO {}", rate_limit) + } } } } @@ -349,6 +341,9 @@ impl fmt::Display for AlterViewOperation { if *deferred { " DEFERRED" } else { "" } ) } + AlterViewOperation::SetStreamingRateLimit { rate_limit } => { + write!(f, "SET STREAMING_RATE_LIMIT TO {}", rate_limit) + } } } } @@ -392,17 +387,6 @@ impl fmt::Display for AlterSubscriptionOperation { AlterSubscriptionOperation::SetSchema { new_schema_name } => { write!(f, "SET SCHEMA {}", new_schema_name) } - AlterSubscriptionOperation::SetParallelism { - parallelism, - deferred, - } => { - write!( - f, - "SET PARALLELISM TO {} {}", - parallelism, - if *deferred { " DEFERRED" } else { "" } - ) - } } } } @@ -428,6 +412,9 @@ impl fmt::Display for AlterSourceOperation { AlterSourceOperation::RefreshSchema => { write!(f, "REFRESH SCHEMA") } + AlterSourceOperation::SetStreamingRateLimit { rate_limit } => { + write!(f, "SET STREAMING_RATE_LIMIT TO {}", rate_limit) + } } } } diff --git a/src/sqlparser/src/ast/legacy_source.rs b/src/sqlparser/src/ast/legacy_source.rs index ad005c49d0a13..03ce1055ff042 100644 --- a/src/sqlparser/src/ast/legacy_source.rs +++ b/src/sqlparser/src/ast/legacy_source.rs @@ -20,14 +20,15 @@ use std::fmt; use itertools::Itertools as _; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use winnow::PResult; use crate::ast::{ AstString, AstVec, ConnectorSchema, Encode, Format, Ident, ObjectName, ParseTo, SqlOption, Value, }; use crate::keywords::Keyword; -use crate::parser::{Parser, ParserError}; -use crate::{impl_fmt_display, impl_parse_to}; +use crate::parser::{Parser, StrError}; +use crate::{impl_fmt_display, impl_parse_to, parser_err}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -64,8 +65,11 @@ impl From for CompatibleSourceSchema { } } -pub fn parse_source_schema(p: &mut Parser) -> Result { +pub fn parse_source_schema(p: &mut Parser<'_>) -> PResult { if let Some(schema_v2) = p.parse_schema()? { + if schema_v2.key_encode.is_some() { + parser_err!("key encode clause is not supported in source schema"); + } Ok(CompatibleSourceSchema::V2(schema_v2)) } else if p.peek_nth_any_of_keywords(0, &[Keyword::ROW]) && p.peek_nth_any_of_keywords(1, &[Keyword::FORMAT]) @@ -104,26 +108,24 @@ pub fn parse_source_schema(p: &mut Parser) -> Result SourceSchema::Bytes, _ => { - return Err(ParserError::ParserError( + parser_err!( "expected JSON | UPSERT_JSON | PROTOBUF | DEBEZIUM_JSON | DEBEZIUM_AVRO \ | AVRO | UPSERT_AVRO | MAXWELL | CANAL_JSON | BYTES | NATIVE after ROW FORMAT" - .to_string(), - )) + ); } }; Ok(CompatibleSourceSchema::RowFormat(schema)) } else { - p.expected("description of the format", p.peek_token()) + p.expected("description of the format") } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum SourceSchema { - Protobuf(ProtobufSchema), - // Keyword::PROTOBUF ProtobufSchema - Json, // Keyword::JSON - DebeziumJson, // Keyword::DEBEZIUM_JSON + Protobuf(ProtobufSchema), // Keyword::PROTOBUF ProtobufSchema + Json, // Keyword::JSON + DebeziumJson, // Keyword::DEBEZIUM_JSON DebeziumMongoJson, UpsertJson, // Keyword::UPSERT_JSON Avro(AvroSchema), // Keyword::AVRO @@ -241,6 +243,7 @@ impl SourceSchema { format, row_encode, row_options, + key_encode: None, } } } @@ -281,7 +284,7 @@ pub struct ProtobufSchema { } impl ParseTo for ProtobufSchema { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!([Keyword::MESSAGE], p); impl_parse_to!(message_name: AstString, p); impl_parse_to!([Keyword::ROW, Keyword::SCHEMA, Keyword::LOCATION], p); @@ -317,8 +320,9 @@ pub struct AvroSchema { pub row_schema_location: AstString, pub use_schema_registry: bool, } + impl ParseTo for AvroSchema { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!([Keyword::ROW, Keyword::SCHEMA, Keyword::LOCATION], p); impl_parse_to!(use_schema_registry => [Keyword::CONFLUENT, Keyword::SCHEMA, Keyword::REGISTRY], p); impl_parse_to!(row_schema_location: AstString, p); @@ -365,7 +369,7 @@ impl fmt::Display for DebeziumAvroSchema { } impl ParseTo for DebeziumAvroSchema { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!( [ Keyword::ROW, @@ -391,19 +395,19 @@ pub struct CsvInfo { pub has_header: bool, } -pub fn get_delimiter(chars: &str) -> Result { +pub fn get_delimiter(chars: &str) -> Result { match chars { "," => Ok(b','), // comma + ";" => Ok(b';'), // semicolon "\t" => Ok(b'\t'), // tab - other => Err(ParserError::ParserError(format!( - "The delimiter should be one of ',', E'\\t', but got {:?}", - other + other => Err(StrError(format!( + "The delimiter should be one of ',', ';', E'\\t', but got {other:?}", ))), } } impl ParseTo for CsvInfo { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(without_header => [Keyword::WITHOUT, Keyword::HEADER], p); impl_parse_to!([Keyword::DELIMITED, Keyword::BY], p); impl_parse_to!(delimiter: AstString, p); diff --git a/src/sqlparser/src/ast/mod.rs b/src/sqlparser/src/ast/mod.rs index 919d87356a3f2..db86c6fb8a47e 100644 --- a/src/sqlparser/src/ast/mod.rs +++ b/src/sqlparser/src/ast/mod.rs @@ -27,10 +27,13 @@ use alloc::{ }; use core::fmt; use core::fmt::Display; +use std::collections::HashSet; +use std::sync::Arc; use itertools::Itertools; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use winnow::PResult; pub use self::data_type::{DataType, StructField}; pub use self::ddl::{ @@ -43,9 +46,9 @@ pub use self::legacy_source::{ }; pub use self::operator::{BinaryOperator, QualifiedOperator, UnaryOperator}; pub use self::query::{ - Cte, Distinct, Fetch, Join, JoinConstraint, JoinOperator, LateralView, OrderByExpr, Query, - Select, SelectItem, SetExpr, SetOperator, TableAlias, TableFactor, TableWithJoins, Top, Values, - With, + Cte, CteInner, Distinct, Fetch, Join, JoinConstraint, JoinOperator, LateralView, OrderByExpr, + Query, Select, SelectItem, SetExpr, SetOperator, TableAlias, TableFactor, TableWithJoins, Top, + Values, With, }; pub use self::statement::*; pub use self::value::{ @@ -57,7 +60,13 @@ pub use crate::ast::ddl::{ AlterViewOperation, }; use crate::keywords::Keyword; -use crate::parser::{IncludeOption, IncludeOptionItem, Parser, ParserError}; +use crate::parser::{IncludeOption, IncludeOptionItem, Parser, ParserError, StrError}; + +pub type RedactSqlOptionKeywordsRef = Arc>; + +tokio::task_local! { + pub static REDACT_SQL_OPTION_KEYWORDS: RedactSqlOptionKeywordsRef; +} pub struct DisplaySeparated<'a, T> where @@ -183,7 +192,7 @@ impl From<&str> for Ident { } impl ParseTo for Ident { - fn parse_to(parser: &mut Parser) -> Result { + fn parse_to(parser: &mut Parser<'_>) -> PResult { parser.parse_identifier() } } @@ -227,7 +236,7 @@ impl fmt::Display for ObjectName { } impl ParseTo for ObjectName { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { p.parse_object_name() } } @@ -408,7 +417,7 @@ pub enum Expr { /// explicitly specified zone AtTimeZone { timestamp: Box, - time_zone: String, + time_zone: Box, }, /// `EXTRACT(DateTimeField FROM )` Extract { @@ -658,7 +667,7 @@ impl fmt::Display for Expr { Expr::AtTimeZone { timestamp, time_zone, - } => write!(f, "{} AT TIME ZONE '{}'", timestamp, time_zone), + } => write!(f, "{} AT TIME ZONE {}", timestamp, time_zone), Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr), Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation), Expr::Nested(ast) => write!(f, "({})", ast), @@ -860,11 +869,7 @@ impl fmt::Display for WindowSpec { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WindowFrame { pub units: WindowFrameUnits, - pub start_bound: WindowFrameBound, - /// The right bound of the `BETWEEN .. AND` clause. The end bound of `None` - /// indicates the shorthand form (e.g. `ROWS 1 PRECEDING`), which must - /// behave the same as `end_bound = WindowFrameBound::CurrentRow`. - pub end_bound: Option, + pub bounds: WindowFrameBounds, pub exclusion: Option, } @@ -874,18 +879,36 @@ pub enum WindowFrameUnits { Rows, Range, Groups, + Session, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum WindowFrameBounds { + Bounds { + start: WindowFrameBound, + /// The right bound of the `BETWEEN .. AND` clause. The end bound of `None` + /// indicates the shorthand form (e.g. `ROWS 1 PRECEDING`), which must + /// behave the same as `end_bound = WindowFrameBound::CurrentRow`. + end: Option, + }, + Gap(Box), } impl fmt::Display for WindowFrame { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(end_bound) = &self.end_bound { - write!( - f, - "{} BETWEEN {} AND {}", - self.units, self.start_bound, end_bound - ) - } else { - write!(f, "{} {}", self.units, self.start_bound) + write!(f, "{} ", self.units)?; + match &self.bounds { + WindowFrameBounds::Bounds { start, end } => { + if let Some(end) = end { + write!(f, "BETWEEN {} AND {}", start, end) + } else { + write!(f, "{}", start) + } + } + WindowFrameBounds::Gap(gap) => { + write!(f, "WITH GAP {}", gap) + } } } } @@ -896,6 +919,7 @@ impl fmt::Display for WindowFrameUnits { WindowFrameUnits::Rows => "ROWS", WindowFrameUnits::Range => "RANGE", WindowFrameUnits::Groups => "GROUPS", + WindowFrameUnits::Session => "SESSION", }) } } @@ -977,6 +1001,7 @@ pub enum ShowObject { Subscription { schema: Option }, Columns { table: ObjectName }, Connection { schema: Option }, + Secret { schema: Option }, Function { schema: Option }, Indexes { table: ObjectName }, Cluster, @@ -1025,6 +1050,7 @@ impl fmt::Display for ShowObject { ShowObject::Jobs => write!(f, "JOBS"), ShowObject::ProcessList => write!(f, "PROCESSLIST"), ShowObject::Subscription { schema } => write!(f, "SUBSCRIPTIONS{}", fmt_schema(schema)), + ShowObject::Secret { schema } => write!(f, "SECRETS{}", fmt_schema(schema)), } } } @@ -1101,6 +1127,7 @@ pub struct ExplainOptions { // explain's plan type pub explain_type: ExplainType, } + impl Default for ExplainOptions { fn default() -> Self { Self { @@ -1110,6 +1137,7 @@ impl Default for ExplainOptions { } } } + impl fmt::Display for ExplainOptions { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let default = Self::default(); @@ -1263,6 +1291,9 @@ pub enum Statement { CreateConnection { stmt: CreateConnectionStatement, }, + CreateSecret { + stmt: CreateSecretStatement, + }, /// CREATE FUNCTION /// /// Postgres: @@ -1283,8 +1314,8 @@ pub enum Statement { or_replace: bool, name: ObjectName, args: Vec, + returns: DataType, /// Optional parameters. - returns: Option, append_only: bool, params: CreateFunctionBody, }, @@ -1387,7 +1418,7 @@ pub enum Statement { Kill(i32), /// DROP Drop(DropStatement), - /// DROP Function + /// DROP FUNCTION DropFunction { if_exists: bool, /// One or more function to drop @@ -1395,6 +1426,14 @@ pub enum Statement { /// `CASCADE` or `RESTRICT` option: Option, }, + /// DROP AGGREGATE + DropAggregate { + if_exists: bool, + /// One or more function to drop + func_desc: Vec, + /// `CASCADE` or `RESTRICT` + option: Option, + }, /// `SET ` /// /// Note: this is not a standard SQL statement, but it is supported by at @@ -1452,6 +1491,7 @@ pub enum Statement { CreateSchema { schema_name: ObjectName, if_not_exists: bool, + user_specified: Option, }, /// CREATE DATABASE CreateDatabase { @@ -1559,14 +1599,14 @@ impl fmt::Display for Statement { write!(f, "DESCRIBE {}", name)?; Ok(()) } - Statement::ShowObjects{ object: show_object, filter} => { + Statement::ShowObjects { object: show_object, filter } => { write!(f, "SHOW {}", show_object)?; if let Some(filter) = filter { write!(f, " {}", filter)?; } Ok(()) } - Statement::ShowCreateObject{ create_type: show_type, name } => { + Statement::ShowCreateObject { create_type: show_type, name } => { write!(f, "SHOW CREATE {} {}", show_type, name)?; Ok(()) } @@ -1580,7 +1620,7 @@ impl fmt::Display for Statement { source, returning, } => { - write!(f, "INSERT INTO {table_name} ", table_name = table_name,)?; + write!(f, "INSERT INTO {table_name} ", table_name = table_name, )?; if !columns.is_empty() { write!(f, "({}) ", display_comma_separated(columns))?; } @@ -1697,9 +1737,7 @@ impl fmt::Display for Statement { or_replace = if *or_replace { "OR REPLACE " } else { "" }, )?; write!(f, "({})", display_comma_separated(args))?; - if let Some(return_type) = returns { - write!(f, " RETURNS {}", return_type)?; - } + write!(f, " RETURNS {}", returns)?; if *append_only { write!(f, " APPEND ONLY")?; } @@ -1790,18 +1828,18 @@ impl fmt::Display for Statement { write!(f, "{}", display_comma_separated( include_column_options.iter().map(|option_item: &IncludeOptionItem| { format!("INCLUDE {}{}{}", - option_item.column_type, + option_item.column_type, if let Some(inner_field) = &option_item.inner_field { format!(" {}", inner_field) } else { "".into() } , if let Some(alias) = &option_item.column_alias { - format!(" AS {}", alias) - } else { - "".into() - } - ) + format!(" AS {}", alias) + } else { + "".into() + } + ) }).collect_vec().as_slice() ))?; } @@ -1860,6 +1898,7 @@ impl fmt::Display for Statement { Statement::DeclareCursor { stmt } => write!(f, "DECLARE {}", stmt,), Statement::FetchCursor { stmt } => write!(f, "FETCH {}", stmt), Statement::CloseCursor { stmt } => write!(f, "CLOSE {}", stmt), + Statement::CreateSecret { stmt } => write!(f, "CREATE SECRET {}", stmt), Statement::AlterDatabase { name, operation } => { write!(f, "ALTER DATABASE {} {}", name, operation) } @@ -1912,6 +1951,22 @@ impl fmt::Display for Statement { } Ok(()) } + Statement::DropAggregate { + if_exists, + func_desc, + option, + } => { + write!( + f, + "DROP AGGREGATE{} {}", + if *if_exists { " IF EXISTS" } else { "" }, + display_comma_separated(func_desc), + )?; + if let Some(op) = option { + write!(f, " {}", op)?; + } + Ok(()) + } Statement::SetVariable { local, variable, @@ -1972,20 +2027,27 @@ impl fmt::Display for Statement { Ok(()) } Statement::Commit { chain } => { - write!(f, "COMMIT{}", if *chain { " AND CHAIN" } else { "" },) + write!(f, "COMMIT{}", if *chain { " AND CHAIN" } else { "" }, ) } Statement::Rollback { chain } => { - write!(f, "ROLLBACK{}", if *chain { " AND CHAIN" } else { "" },) + write!(f, "ROLLBACK{}", if *chain { " AND CHAIN" } else { "" }, ) } Statement::CreateSchema { schema_name, if_not_exists, - } => write!( - f, - "CREATE SCHEMA {if_not_exists}{name}", - if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }, - name = schema_name - ), + user_specified, + } => { + write!( + f, + "CREATE SCHEMA {if_not_exists}{name}", + if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }, + name = schema_name + )?; + if let Some(user) = user_specified { + write!(f, " AUTHORIZATION {}", user)?; + } + Ok(()) + }, Statement::Grant { privileges, objects, @@ -2072,7 +2134,7 @@ impl fmt::Display for Statement { Statement::AlterUser(statement) => { write!(f, "ALTER USER {}", statement) } - Statement::AlterSystem{param, value} => { + Statement::AlterSystem { param, value } => { f.write_str("ALTER SYSTEM SET ")?; write!( f, @@ -2490,6 +2552,7 @@ pub enum ObjectType { Database, User, Connection, + Secret, Subscription, } @@ -2505,6 +2568,7 @@ impl fmt::Display for ObjectType { ObjectType::Sink => "SINK", ObjectType::Database => "DATABASE", ObjectType::User => "USER", + ObjectType::Secret => "SECRET", ObjectType::Connection => "CONNECTION", ObjectType::Subscription => "SUBSCRIPTION", }) @@ -2512,7 +2576,7 @@ impl fmt::Display for ObjectType { } impl ParseTo for ObjectType { - fn parse_to(parser: &mut Parser) -> Result { + fn parse_to(parser: &mut Parser<'_>) -> PResult { let object_type = if parser.parse_keyword(Keyword::TABLE) { ObjectType::Table } else if parser.parse_keyword(Keyword::VIEW) { @@ -2533,12 +2597,13 @@ impl ParseTo for ObjectType { ObjectType::User } else if parser.parse_keyword(Keyword::CONNECTION) { ObjectType::Connection + } else if parser.parse_keyword(Keyword::SECRET) { + ObjectType::Secret } else if parser.parse_keyword(Keyword::SUBSCRIPTION) { ObjectType::Subscription } else { return parser.expected( - "TABLE, VIEW, INDEX, MATERIALIZED VIEW, SOURCE, SINK, SUBSCRIPTION, SCHEMA, DATABASE, USER or CONNECTION after DROP", - parser.peek_token(), + "TABLE, VIEW, INDEX, MATERIALIZED VIEW, SOURCE, SINK, SUBSCRIPTION, SCHEMA, DATABASE, USER, SECRET or CONNECTION after DROP", ); }; Ok(object_type) @@ -2554,7 +2619,17 @@ pub struct SqlOption { impl fmt::Display for SqlOption { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} = {}", self.name, self.value) + let should_redact = REDACT_SQL_OPTION_KEYWORDS + .try_with(|keywords| { + let sql_option_name = self.name.real_value().to_lowercase(); + keywords.iter().any(|k| sql_option_name.contains(k)) + }) + .unwrap_or(false); + if should_redact { + write!(f, "{} = [REDACTED]", self.name) + } else { + write!(f, "{} = {}", self.name, self.value) + } } } @@ -2706,7 +2781,6 @@ impl fmt::Display for DropFunctionOption { /// Function describe in DROP FUNCTION. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct FunctionDesc { pub name: ObjectName, pub args: Option>, @@ -2811,6 +2885,7 @@ impl fmt::Display for FunctionBehavior { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum FunctionDefinition { + Identifier(String), SingleQuotedDef(String), DoubleDollarDef(String), } @@ -2818,6 +2893,7 @@ pub enum FunctionDefinition { impl fmt::Display for FunctionDefinition { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + FunctionDefinition::Identifier(s) => write!(f, "{s}")?, FunctionDefinition::SingleQuotedDef(s) => write!(f, "'{s}'")?, FunctionDefinition::DoubleDollarDef(s) => write!(f, "$${s}$$")?, } @@ -2829,6 +2905,7 @@ impl FunctionDefinition { /// Returns the function definition as a string slice. pub fn as_str(&self) -> &str { match self { + FunctionDefinition::Identifier(s) => s, FunctionDefinition::SingleQuotedDef(s) => s, FunctionDefinition::DoubleDollarDef(s) => s, } @@ -2837,6 +2914,7 @@ impl FunctionDefinition { /// Returns the function definition as a string. pub fn into_string(self) -> String { match self { + FunctionDefinition::Identifier(s) => s, FunctionDefinition::SingleQuotedDef(s) => s, FunctionDefinition::DoubleDollarDef(s) => s, } @@ -2887,8 +2965,8 @@ impl fmt::Display for TableColumnDef { pub struct CreateFunctionBody { /// LANGUAGE lang_name pub language: Option, - - pub runtime: Option, + /// RUNTIME runtime_name + pub runtime: Option, /// IMMUTABLE | STABLE | VOLATILE pub behavior: Option, @@ -2909,11 +2987,9 @@ impl fmt::Display for CreateFunctionBody { if let Some(language) = &self.language { write!(f, " LANGUAGE {language}")?; } - if let Some(runtime) = &self.runtime { write!(f, " RUNTIME {runtime}")?; } - if let Some(behavior) = &self.behavior { write!(f, " {behavior}")?; } @@ -2932,6 +3008,7 @@ impl fmt::Display for CreateFunctionBody { Ok(()) } } + #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct CreateFunctionWithOptions { @@ -2948,7 +3025,7 @@ impl CreateFunctionWithOptions { /// TODO(kwannoel): Generate from the struct definition instead. impl TryFrom> for CreateFunctionWithOptions { - type Error = ParserError; + type Error = StrError; fn try_from(with_options: Vec) -> Result { let mut always_retry_on_network_error = None; @@ -2956,10 +3033,7 @@ impl TryFrom> for CreateFunctionWithOptions { if option.name.to_string().to_lowercase() == "always_retry_on_network_error" { always_retry_on_network_error = Some(option.value == Value::Boolean(true)); } else { - return Err(ParserError::ParserError(format!( - "Unsupported option: {}", - option.name - ))); + return Err(StrError(format!("Unsupported option: {}", option.name))); } } Ok(Self { @@ -3003,22 +3077,6 @@ impl fmt::Display for CreateFunctionUsing { } } -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum FunctionRuntime { - QuickJs, - Deno, -} - -impl fmt::Display for FunctionRuntime { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FunctionRuntime::QuickJs => write!(f, "quickjs"), - FunctionRuntime::Deno => write!(f, "deno"), - } - } -} - #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum CreateFunctionType { @@ -3130,6 +3188,12 @@ impl fmt::Display for DiscardType { } } +impl Statement { + pub fn to_redacted_string(&self, keywords: RedactSqlOptionKeywordsRef) -> String { + REDACT_SQL_OPTION_KEYWORDS.sync_scope(keywords, || self.to_string()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -3320,7 +3384,7 @@ mod tests { as_: Some(FunctionDefinition::SingleQuotedDef("SELECT 1".to_string())), return_: None, using: None, - runtime: Some(FunctionRuntime::Deno), + runtime: Some(Ident::new_unchecked("deno")), function_type: Some(CreateFunctionType::AsyncGenerator), }, with_options: CreateFunctionWithOptions { diff --git a/src/sqlparser/src/ast/query.rs b/src/sqlparser/src/ast/query.rs index 5425864bf4e5c..83e84907a1091 100644 --- a/src/sqlparser/src/ast/query.rs +++ b/src/sqlparser/src/ast/query.rs @@ -282,20 +282,28 @@ impl fmt::Display for With { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Cte { pub alias: TableAlias, - pub query: Query, - pub from: Option, + pub cte_inner: CteInner, } impl fmt::Display for Cte { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} AS ({})", self.alias, self.query)?; - if let Some(ref fr) = self.from { - write!(f, " FROM {}", fr)?; + match &self.cte_inner { + CteInner::Query(query) => write!(f, "{} AS ({})", self.alias, query)?, + CteInner::ChangeLog(ident) => { + write!(f, "{} AS changelog from {}", self.alias, ident.value)? + } } Ok(()) } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum CteInner { + Query(Query), + ChangeLog(Ident), +} + /// One item of the comma-separated list following `SELECT` #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] diff --git a/src/sqlparser/src/ast/statement.rs b/src/sqlparser/src/ast/statement.rs index 8f2c365efbcd4..b787b066b5622 100644 --- a/src/sqlparser/src/ast/statement.rs +++ b/src/sqlparser/src/ast/statement.rs @@ -13,25 +13,29 @@ // limitations under the License. use core::fmt; +use core::fmt::Formatter; use std::fmt::Write; use itertools::Itertools; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use winnow::PResult; use super::ddl::SourceWatermark; use super::legacy_source::{parse_source_schema, CompatibleSourceSchema}; -use super::{EmitMode, Ident, ObjectType, Query}; +use super::{EmitMode, Ident, ObjectType, Query, Value}; use crate::ast::{ display_comma_separated, display_separated, ColumnDef, ObjectName, SqlOption, TableConstraint, }; use crate::keywords::Keyword; -use crate::parser::{IncludeOption, IsOptional, Parser, ParserError, UPSTREAM_SOURCE_KEY}; +use crate::parser::{IncludeOption, IsOptional, Parser, UPSTREAM_SOURCE_KEY}; +use crate::parser_err; +use crate::parser_v2::literal_u32; use crate::tokenizer::Token; /// Consumes token from the parser into an AST node. pub trait ParseTo: Sized { - fn parse_to(parser: &mut Parser) -> Result; + fn parse_to(parser: &mut Parser<'_>) -> PResult; } #[macro_export] @@ -90,17 +94,29 @@ pub struct CreateSourceStatement { pub include_column_options: IncludeOption, } +/// FORMAT means how to get the operation(Insert/Delete) from the input. +/// +/// Check `CONNECTORS_COMPATIBLE_FORMATS` for what `FORMAT ... ENCODE ...` combinations are allowed. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Format { + /// The format is the same with RisingWave's internal representation. + /// Used internally for schema change Native, - None, // Keyword::NONE - Debezium, // Keyword::DEBEZIUM - DebeziumMongo, // Keyword::DEBEZIUM_MONGO - Maxwell, // Keyword::MAXWELL - Canal, // Keyword::CANAL - Upsert, // Keyword::UPSERT - Plain, // Keyword::PLAIN + /// for self-explanatory sources like iceberg, they have their own format, and should not be specified by user. + None, + // Keyword::DEBEZIUM + Debezium, + // Keyword::DEBEZIUM_MONGO + DebeziumMongo, + // Keyword::MAXWELL + Maxwell, + // Keyword::CANAL + Canal, + // Keyword::UPSERT + Upsert, + // Keyword::PLAIN + Plain, } // TODO: unify with `from_keyword` @@ -124,7 +140,7 @@ impl fmt::Display for Format { } impl Format { - pub fn from_keyword(s: &str) -> Result { + pub fn from_keyword(s: &str) -> PResult { Ok(match s { "DEBEZIUM" => Format::Debezium, "DEBEZIUM_MONGO" => Format::DebeziumMongo, @@ -132,18 +148,16 @@ impl Format { "CANAL" => Format::Canal, "PLAIN" => Format::Plain, "UPSERT" => Format::Upsert, - "NATIVE" => Format::Native, // used internally for schema change - "NONE" => Format::None, // used by iceberg - _ => { - return Err(ParserError::ParserError( - "expected CANAL | PROTOBUF | DEBEZIUM | MAXWELL | PLAIN | NATIVE | NONE after FORMAT" - .to_string(), - )) - } + "NATIVE" => Format::Native, + "NONE" => Format::None, + _ => parser_err!( + "expected CANAL | PROTOBUF | DEBEZIUM | MAXWELL | PLAIN | NATIVE | NONE after FORMAT" + ), }) } } +/// Check `CONNECTORS_COMPATIBLE_FORMATS` for what `FORMAT ... ENCODE ...` combinations are allowed. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Encode { @@ -152,7 +166,11 @@ pub enum Encode { Protobuf, // Keyword::PROTOBUF Json, // Keyword::JSON Bytes, // Keyword::BYTES - None, // Keyword::None + /// for self-explanatory sources like iceberg, they have their own format, and should not be specified by user. + None, + Text, // Keyword::TEXT + /// The encode is the same with RisingWave's internal representation. + /// Used internally for schema change Native, Template, Parquet, @@ -174,27 +192,28 @@ impl fmt::Display for Encode { Encode::Template => "TEMPLATE", Encode::None => "NONE", Encode::Parquet => "PARQUET", + Encode::Text => "TEXT", } ) } } impl Encode { - pub fn from_keyword(s: &str) -> Result { + pub fn from_keyword(s: &str) -> PResult { Ok(match s { "AVRO" => Encode::Avro, + "TEXT" => Encode::Text, "BYTES" => Encode::Bytes, "CSV" => Encode::Csv, "PROTOBUF" => Encode::Protobuf, "JSON" => Encode::Json, "TEMPLATE" => Encode::Template, - "NATIVE" => Encode::Native, // used internally for schema change + "NATIVE" => Encode::Native, + "NONE" => Encode::None, "PARQUET" => Encode::Parquet, - "NONE" => Encode::None, // used by iceberg - _ => return Err(ParserError::ParserError( - "expected AVRO | BYTES | CSV | PROTOBUF | JSON | NATIVE | TEMPLATE | NONE after Encode" - .to_string(), - )), + _ => parser_err!( + "expected AVRO | BYTES | CSV | PROTOBUF | JSON | NATIVE | TEMPLATE |PARQUET| NONE after Encode" + ), }) } } @@ -205,9 +224,11 @@ pub struct ConnectorSchema { pub format: Format, pub row_encode: Encode, pub row_options: Vec, + + pub key_encode: Option, } -impl Parser { +impl Parser<'_> { /// Peek the next tokens to see if it is `FORMAT` or `ROW FORMAT` (for compatibility). fn peek_source_schema_format(&mut self) -> bool { (self.peek_nth_any_of_keywords(0, &[Keyword::ROW]) @@ -220,7 +241,7 @@ impl Parser { &mut self, connector: &str, cdc_source_job: bool, - ) -> Result { + ) -> PResult { // row format for cdc source must be debezium json // row format for nexmark source must be native // default row format for datagen source is native @@ -237,10 +258,10 @@ impl Parser { if self.peek_source_schema_format() { let schema = parse_source_schema(self)?.into_v2(); if schema != expected { - return Err(ParserError::ParserError(format!( + parser_err!( "Row format for CDC connectors should be \ either omitted or set to `{expected}`", - ))); + ); } } Ok(expected.into()) @@ -249,10 +270,10 @@ impl Parser { if self.peek_source_schema_format() { let schema = parse_source_schema(self)?.into_v2(); if schema != expected { - return Err(ParserError::ParserError(format!( + parser_err!( "Row format for nexmark connectors should be \ either omitted or set to `{expected}`", - ))); + ); } } Ok(expected.into()) @@ -267,10 +288,10 @@ impl Parser { if self.peek_source_schema_format() { let schema = parse_source_schema(self)?.into_v2(); if schema != expected { - return Err(ParserError::ParserError(format!( + parser_err!( "Row format for iceberg connectors should be \ either omitted or set to `{expected}`", - ))); + ); } } Ok(expected.into()) @@ -280,7 +301,7 @@ impl Parser { } /// Parse `FORMAT ... ENCODE ... (...)`. - pub fn parse_schema(&mut self) -> Result, ParserError> { + pub fn parse_schema(&mut self) -> PResult> { if !self.parse_keyword(Keyword::FORMAT) { return Ok(None); } @@ -294,10 +315,19 @@ impl Parser { let row_encode = Encode::from_keyword(&s)?; let row_options = self.parse_options()?; + let key_encode = if self.parse_keywords(&[Keyword::KEY, Keyword::ENCODE]) { + Some(Encode::from_keyword( + self.parse_identifier()?.value.to_ascii_uppercase().as_str(), + )?) + } else { + None + }; + Ok(Some(ConnectorSchema { format, row_encode, row_options, + key_encode, })) } } @@ -308,6 +338,7 @@ impl ConnectorSchema { format: Format::Plain, row_encode: Encode::Json, row_options: Vec::new(), + key_encode: None, } } @@ -317,6 +348,7 @@ impl ConnectorSchema { format: Format::Debezium, row_encode: Encode::Json, row_options: Vec::new(), + key_encode: None, } } @@ -325,6 +357,7 @@ impl ConnectorSchema { format: Format::DebeziumMongo, row_encode: Encode::Json, row_options: Vec::new(), + key_encode: None, } } @@ -334,6 +367,7 @@ impl ConnectorSchema { format: Format::Native, row_encode: Encode::Native, row_options: Vec::new(), + key_encode: None, } } @@ -344,6 +378,7 @@ impl ConnectorSchema { format: Format::None, row_encode: Encode::None, row_options: Vec::new(), + key_encode: None, } } @@ -365,7 +400,7 @@ impl fmt::Display for ConnectorSchema { } impl ParseTo for CreateSourceStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(if_not_exists => [Keyword::IF, Keyword::NOT, Keyword::EXISTS], p); impl_parse_to!(source_name: ObjectName, p); @@ -379,9 +414,11 @@ impl ParseTo for CreateSourceStatement { .iter() .find(|&opt| opt.name.real_value() == UPSTREAM_SOURCE_KEY); let connector: String = option.map(|opt| opt.value.to_string()).unwrap_or_default(); - // The format of cdc source job is fixed to `FORMAT PLAIN ENCODE JSON` - let cdc_source_job = - connector.contains("-cdc") && columns.is_empty() && constraints.is_empty(); + let cdc_source_job = connector.contains("-cdc"); + if cdc_source_job && (!columns.is_empty() || !constraints.is_empty()) { + parser_err!("CDC source cannot define columns and constraints"); + } + // row format for nexmark source must be native // default row format for datagen source is native let source_schema = p.parse_source_schema_with_connector(&connector, cdc_source_job)?; @@ -475,7 +512,6 @@ impl fmt::Display for CreateSink { } } } - // sql_grammar!(CreateSinkStatement { // if_not_exists => [Keyword::IF, Keyword::NOT, Keyword::EXISTS], // sink_name: Ident, @@ -497,7 +533,7 @@ pub struct CreateSinkStatement { } impl ParseTo for CreateSinkStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(if_not_exists => [Keyword::IF, Keyword::NOT, Keyword::EXISTS], p); impl_parse_to!(sink_name: ObjectName, p); @@ -517,22 +553,20 @@ impl ParseTo for CreateSinkStatement { let query = Box::new(p.parse_query()?); CreateSink::AsQuery(query) } else { - p.expected("FROM or AS after CREATE SINK sink_name", p.peek_token())? + p.expected("FROM or AS after CREATE SINK sink_name")? }; - let emit_mode = p.parse_emit_mode()?; + let emit_mode: Option = p.parse_emit_mode()?; // This check cannot be put into the `WithProperties::parse_to`, since other // statements may not need the with properties. if !p.peek_nth_any_of_keywords(0, &[Keyword::WITH]) && into_table_name.is_none() { - p.expected("WITH", p.peek_token())? + p.expected("WITH")? } impl_parse_to!(with_properties: WithProperties, p); if with_properties.0.is_empty() && into_table_name.is_none() { - return Err(ParserError::ParserError( - "sink properties not provided".to_string(), - )); + parser_err!("sink properties not provided"); } let sink_schema = p.parse_schema()?; @@ -589,7 +623,7 @@ pub struct CreateSubscriptionStatement { } impl ParseTo for CreateSubscriptionStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(if_not_exists => [Keyword::IF, Keyword::NOT, Keyword::EXISTS], p); impl_parse_to!(subscription_name: ObjectName, p); @@ -597,10 +631,7 @@ impl ParseTo for CreateSubscriptionStatement { impl_parse_to!(from_name: ObjectName, p); from_name } else { - p.expected( - "FROM after CREATE SUBSCRIPTION subscription_name", - p.peek_token(), - )? + p.expected("FROM after CREATE SUBSCRIPTION subscription_name")? }; // let emit_mode = p.parse_emit_mode()?; @@ -608,14 +639,12 @@ impl ParseTo for CreateSubscriptionStatement { // This check cannot be put into the `WithProperties::parse_to`, since other // statements may not need the with properties. if !p.peek_nth_any_of_keywords(0, &[Keyword::WITH]) { - p.expected("WITH", p.peek_token())? + p.expected("WITH")? } impl_parse_to!(with_properties: WithProperties, p); if with_properties.0.is_empty() { - return Err(ParserError::ParserError( - "subscription properties not provided".to_string(), - )); + parser_err!("subscription properties not provided"); } Ok(Self { @@ -626,6 +655,7 @@ impl ParseTo for CreateSubscriptionStatement { }) } } + impl fmt::Display for CreateSubscriptionStatement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut v: Vec = vec![]; @@ -657,6 +687,7 @@ impl fmt::Display for DeclareCursor { v.iter().join(" ").fmt(f) } } + // sql_grammar!(DeclareCursorStatement { // cursor_name: Ident, // [Keyword::SUBSCRIPTION] @@ -674,7 +705,7 @@ pub struct DeclareCursorStatement { } impl ParseTo for DeclareCursorStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(cursor_name: ObjectName, p); let declare_cursor = if !p.parse_keyword(Keyword::SUBSCRIPTION) { @@ -695,6 +726,7 @@ impl ParseTo for DeclareCursorStatement { }) } } + impl fmt::Display for DeclareCursorStatement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut v: Vec = vec![]; @@ -716,14 +748,11 @@ pub struct FetchCursorStatement { } impl ParseTo for FetchCursorStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { let count = if p.parse_keyword(Keyword::NEXT) { 1 } else { - let count_str = p.parse_number_value()?; - count_str.parse::().map_err(|e| { - ParserError::ParserError(format!("Could not parse '{}' as i32: {}", count_str, e)) - })? + literal_u32(p)? }; p.expect_keyword(Keyword::FROM)?; impl_parse_to!(cursor_name: ObjectName, p); @@ -756,7 +785,7 @@ pub struct CloseCursorStatement { } impl ParseTo for CloseCursorStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { let cursor_name = if p.parse_keyword(Keyword::ALL) { None } else { @@ -766,6 +795,7 @@ impl ParseTo for CloseCursorStatement { Ok(Self { cursor_name }) } } + impl fmt::Display for CloseCursorStatement { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut v: Vec = vec![]; @@ -792,14 +822,12 @@ pub struct CreateConnectionStatement { } impl ParseTo for CreateConnectionStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(if_not_exists => [Keyword::IF, Keyword::NOT, Keyword::EXISTS], p); impl_parse_to!(connection_name: ObjectName, p); impl_parse_to!(with_properties: WithProperties, p); if with_properties.0.is_empty() { - return Err(ParserError::ParserError( - "connection properties not provided".to_string(), - )); + parser_err!("connection properties not provided"); } Ok(Self { @@ -820,6 +848,47 @@ impl fmt::Display for CreateConnectionStatement { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct CreateSecretStatement { + pub if_not_exists: bool, + pub secret_name: ObjectName, + pub credential: Value, + pub with_properties: WithProperties, +} + +impl ParseTo for CreateSecretStatement { + fn parse_to(parser: &mut Parser<'_>) -> PResult { + impl_parse_to!(if_not_exists => [Keyword::IF, Keyword::NOT, Keyword::EXISTS], parser); + impl_parse_to!(secret_name: ObjectName, parser); + impl_parse_to!(with_properties: WithProperties, parser); + let mut credential = Value::Null; + if parser.parse_keyword(Keyword::AS) { + credential = parser.parse_value()?; + } + Ok(Self { + if_not_exists, + secret_name, + credential, + with_properties, + }) + } +} + +impl fmt::Display for CreateSecretStatement { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut v: Vec = vec![]; + impl_fmt_display!(if_not_exists => [Keyword::IF, Keyword::NOT, Keyword::EXISTS], v, self); + impl_fmt_display!(secret_name, v, self); + impl_fmt_display!(with_properties, v, self); + if self.credential != Value::Null { + v.push("AS".to_string()); + impl_fmt_display!(credential, v, self); + } + v.iter().join(" ").fmt(f) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct AstVec(pub Vec); @@ -835,7 +904,7 @@ impl fmt::Display for AstVec { pub struct WithProperties(pub Vec); impl ParseTo for WithProperties { - fn parse_to(parser: &mut Parser) -> Result { + fn parse_to(parser: &mut Parser<'_>) -> PResult { Ok(Self( parser.parse_options_with_preceding_keyword(Keyword::WITH)?, )) @@ -878,7 +947,7 @@ pub struct RowSchemaLocation { } impl ParseTo for RowSchemaLocation { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!([Keyword::ROW, Keyword::SCHEMA, Keyword::LOCATION], p); impl_parse_to!(value: AstString, p); Ok(Self { value }) @@ -901,7 +970,7 @@ impl fmt::Display for RowSchemaLocation { pub struct AstString(pub String); impl ParseTo for AstString { - fn parse_to(parser: &mut Parser) -> Result { + fn parse_to(parser: &mut Parser<'_>) -> PResult { Ok(Self(parser.parse_literal_string()?)) } } @@ -924,7 +993,7 @@ pub enum AstOption { } impl ParseTo for AstOption { - fn parse_to(parser: &mut Parser) -> Result { + fn parse_to(parser: &mut Parser<'_>) -> PResult { match T::parse_to(parser) { Ok(t) => Ok(AstOption::Some(t)), Err(_) => Ok(AstOption::None), @@ -1044,17 +1113,14 @@ impl UserOptionsBuilder { } impl ParseTo for UserOptions { - fn parse_to(parser: &mut Parser) -> Result { + fn parse_to(parser: &mut Parser<'_>) -> PResult { let mut builder = UserOptionsBuilder::default(); let add_option = |item: &mut Option, user_option| { let old_value = item.replace(user_option); if old_value.is_some() { - Err(ParserError::ParserError( - "conflicting or redundant options".to_string(), - )) - } else { - Ok(()) + parser_err!("conflicting or redundant options"); } + Ok(()) }; let _ = parser.parse_keyword(Keyword::WITH); loop { @@ -1064,6 +1130,7 @@ impl ParseTo for UserOptions { } if let Token::Word(ref w) = token.token { + let checkpoint = *parser; parser.next_token(); let (item_mut_ref, user_option) = match w.keyword { Keyword::SUPERUSER => (&mut builder.super_user, UserOption::SuperUser), @@ -1096,10 +1163,10 @@ impl ParseTo for UserOptions { (&mut builder.password, UserOption::OAuth(options)) } _ => { - parser.expected( + parser.expected_at( + checkpoint, "SUPERUSER | NOSUPERUSER | CREATEDB | NOCREATEDB | LOGIN \ | NOLOGIN | CREATEUSER | NOCREATEUSER | [ENCRYPTED] PASSWORD | NULL | OAUTH", - token, )?; unreachable!() } @@ -1109,7 +1176,6 @@ impl ParseTo for UserOptions { parser.expected( "SUPERUSER | NOSUPERUSER | CREATEDB | NOCREATEDB | LOGIN | NOLOGIN \ | CREATEUSER | NOCREATEUSER | [ENCRYPTED] PASSWORD | NULL | OAUTH", - token, )? } } @@ -1128,7 +1194,7 @@ impl fmt::Display for UserOptions { } impl ParseTo for CreateUserStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(user_name: ObjectName, p); impl_parse_to!(with_options: UserOptions, p); @@ -1171,7 +1237,7 @@ impl fmt::Display for AlterUserStatement { } impl ParseTo for AlterUserStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(user_name: ObjectName, p); impl_parse_to!(mode: AlterUserMode, p); @@ -1180,7 +1246,7 @@ impl ParseTo for AlterUserStatement { } impl ParseTo for AlterUserMode { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { if p.parse_keyword(Keyword::RENAME) { p.expect_keyword(Keyword::TO)?; impl_parse_to!(new_name: ObjectName, p); @@ -1213,7 +1279,7 @@ pub struct DropStatement { // drop_mode: AstOption, // }); impl ParseTo for DropStatement { - fn parse_to(p: &mut Parser) -> Result { + fn parse_to(p: &mut Parser<'_>) -> PResult { impl_parse_to!(object_type: ObjectType, p); impl_parse_to!(if_exists => [Keyword::IF, Keyword::EXISTS], p); let object_name = p.parse_object_name()?; @@ -1246,13 +1312,13 @@ pub enum DropMode { } impl ParseTo for DropMode { - fn parse_to(parser: &mut Parser) -> Result { + fn parse_to(parser: &mut Parser<'_>) -> PResult { let drop_mode = if parser.parse_keyword(Keyword::CASCADE) { DropMode::Cascade } else if parser.parse_keyword(Keyword::RESTRICT) { DropMode::Restrict } else { - return parser.expected("CASCADE | RESTRICT", parser.peek_token()); + return parser.expected("CASCADE | RESTRICT"); }; Ok(drop_mode) } diff --git a/src/sqlparser/src/ast/value.rs b/src/sqlparser/src/ast/value.rs index 2ce52f3c18bf9..79f2a6ebd99ca 100644 --- a/src/sqlparser/src/ast/value.rs +++ b/src/sqlparser/src/ast/value.rs @@ -17,6 +17,8 @@ use core::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use super::ObjectName; + /// Primitive SQL values such as number and string #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -57,6 +59,8 @@ pub enum Value { }, /// `NULL` value Null, + /// name of the reference to secret + Ref(ObjectName), } impl fmt::Display for Value { @@ -111,6 +115,7 @@ impl fmt::Display for Value { Ok(()) } Value::Null => write!(f, "NULL"), + Value::Ref(v) => write!(f, "ref secret {}", v), } } } diff --git a/src/sqlparser/src/bin/sqlparser.rs b/src/sqlparser/src/bin/sqlparser.rs index be2ec51bc78fc..57a984a8c1cd1 100644 --- a/src/sqlparser/src/bin/sqlparser.rs +++ b/src/sqlparser/src/bin/sqlparser.rs @@ -1,3 +1,19 @@ +// 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. + +#![feature(register_tool)] +#![register_tool(rw)] +#![allow(rw::format_error)] // test code + use std::io; use risingwave_sqlparser::parser::Parser; @@ -12,5 +28,14 @@ fn main() { let mut buffer = String::new(); io::stdin().read_line(&mut buffer).unwrap(); let result = Parser::parse_sql(&buffer); - println!("{:#?}", result); + match result { + Ok(statements) => { + for statement in statements { + println!("{:#?}", statement); + } + } + Err(e) => { + eprintln!("{}", e); + } + } } diff --git a/src/sqlparser/src/keywords.rs b/src/sqlparser/src/keywords.rs index 5647fde58e4e8..fc5971bf4640b 100644 --- a/src/sqlparser/src/keywords.rs +++ b/src/sqlparser/src/keywords.rs @@ -12,7 +12,7 @@ //! This module defines //! 1) a list of constants for every keyword that -//! can appear in [crate::tokenizer::Word::keyword]: +//! can appear in [crate::tokenizer::Word::keyword]: //! pub const KEYWORD = "KEYWORD" //! 2) an `ALL_KEYWORDS` array with every keyword in it //! This is not a list of *reserved* keywords: some of these can be @@ -22,7 +22,7 @@ //! As a matter of fact, most of these keywords are not used at all //! and could be removed. //! 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a -//! "table alias" context. +//! "table alias" context. use core::fmt; @@ -59,7 +59,7 @@ macro_rules! define_keywords { ]; $(kw_def!($ident $(= $string_keyword)?);)* - pub const ALL_KEYWORDS: &[&str] = &[ + pub const ALL_KEYWORDS: &[&'static str] = &[ $($ident),* ]; }; @@ -248,6 +248,7 @@ define_keywords!( FUNCTION, FUNCTIONS, FUSION, + GAP, GENERATOR, GET, GLOBAL, @@ -411,9 +412,7 @@ define_keywords!( REFERENCES, REFERENCING, REFRESH, - REGCLASS, REGISTRY, - REGPROC, REGR_AVGX, REGR_AVGY, REGR_COUNT, @@ -450,6 +449,8 @@ define_keywords!( SCROLL, SEARCH, SECOND, + SECRET, + SECRETS, SELECT, SENSITIVE, SEQUENCE, @@ -487,6 +488,7 @@ define_keywords!( STDDEV_SAMP, STDIN, STORED, + STREAMING_RATE_LIMIT, STRING, STRUCT, SUBMULTISET, @@ -650,7 +652,7 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[ /// Can't be used as a column or table name in PostgreSQL. /// /// This list is taken from the following table, for all "reserved" words in the PostgreSQL column, -/// includinhg "can be function or type" and "requires AS". +/// including "can be function or type" and "requires AS". /// /// `SELECT` and `WITH` were commented out because the following won't parse: /// `SELECT (SELECT 1)` or `SELECT (WITH a AS (SELECT 1) SELECT 1)` diff --git a/src/sqlparser/src/lib.rs b/src/sqlparser/src/lib.rs index 612b11078eac4..a102e5428edae 100644 --- a/src/sqlparser/src/lib.rs +++ b/src/sqlparser/src/lib.rs @@ -45,6 +45,7 @@ extern crate alloc; pub mod ast; pub mod keywords; pub mod parser; +pub mod parser_v2; pub mod tokenizer; #[doc(hidden)] diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index 2cc74c8d0a392..60e9da014f10d 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -24,14 +24,15 @@ use core::fmt; use itertools::Itertools; use tracing::{debug, instrument}; +use winnow::combinator::{alt, cut_err, dispatch, fail, opt, peek, preceded, repeat, separated}; +use winnow::{PResult, Parser as _}; -use self::ddl::AlterSubscriptionOperation; -use crate::ast::ddl::{ - AlterConnectionOperation, AlterDatabaseOperation, AlterFunctionOperation, AlterIndexOperation, - AlterSchemaOperation, AlterSinkOperation, AlterViewOperation, SourceWatermark, -}; -use crate::ast::{ParseTo, *}; +use crate::ast::*; use crate::keywords::{self, Keyword}; +use crate::parser_v2; +use crate::parser_v2::{ + dollar_quoted_string, keyword, literal_i64, literal_uint, single_quoted_string, ParserExt as _, +}; use crate::tokenizer::*; pub(crate) const UPSTREAM_SOURCE_KEY: &str = "connector"; @@ -49,14 +50,33 @@ impl ParserError { } } } + +#[derive(Debug, thiserror::Error)] +#[error("{0}")] +pub struct StrError(pub String); + // Use `Parser::expected` instead, if possible #[macro_export] macro_rules! parser_err { - ($MSG:expr) => { - Err(ParserError::ParserError($MSG.to_string())) + ($($arg:tt)*) => { + return Err(winnow::error::ErrMode::Backtrack(>::from_external_error( + &Parser::default(), + winnow::error::ErrorKind::Fail, + $crate::parser::StrError(format!($($arg)*)), + ))) }; } +impl From for winnow::error::ErrMode { + fn from(e: StrError) -> Self { + winnow::error::ErrMode::Backtrack(>::from_external_error( + &Parser::default(), + winnow::error::ErrorKind::Fail, + e, + )) + } +} + // Returns a successful result if the optional expression is some macro_rules! return_ok_if_some { ($e:expr) => {{ @@ -152,7 +172,7 @@ type ColumnsDefTuple = ( /// Reference: /// -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum Precedence { Zero = 0, LogicalOr, // 5 in upstream @@ -167,61 +187,79 @@ pub enum Precedence { PlusMinus, // 30 in upstream MulDiv, // 40 in upstream Exp, + At, + Collate, UnaryPosNeg, PostfixFactorial, Array, DoubleColon, // 50 in upstream } -pub struct Parser { - tokens: Vec, - /// The index of the first unprocessed token in `self.tokens` - index: usize, - /// Since we cannot distinguish `>>` and double `>`, so use `angle_brackets_num` to store the - /// number of `<` to match `>` in sql like `struct>`. - angle_brackets_num: i32, - /// It's important that already in named Array or not. so use this field check in or not. - /// Consider 0 is you're not in named Array. if more than 0 is you're in named Array - array_depth: usize, - /// We cannot know current array should be keep named or not, so by using this field store - /// every depth of array that should be keep named or not. - array_named_stack: Vec, -} - -impl Parser { - /// Parse the specified tokens - pub fn new(tokens: Vec) -> Self { - Parser { - tokens, - index: 0, - angle_brackets_num: 0, - array_depth: 0, - array_named_stack: Vec::new(), - } - } +#[derive(Clone, Copy, Default)] +pub struct Parser<'a>(pub(crate) &'a [TokenWithLocation]); +impl Parser<'_> { /// Parse a SQL statement and produce an Abstract Syntax Tree (AST) #[instrument(level = "debug")] pub fn parse_sql(sql: &str) -> Result, ParserError> { let mut tokenizer = Tokenizer::new(sql); let tokens = tokenizer.tokenize_with_location()?; - let mut parser = Parser::new(tokens); + let parser = Parser(&tokens); + let stmts = Parser::parse_statements.parse(parser).map_err(|e| { + // append SQL context to the error message, e.g.: + // LINE 1: SELECT 1::int(2); + let loc = match tokens.get(e.offset()) { + Some(token) => token.location.clone(), + None => { + // get location of EOF + Location { + line: sql.lines().count() as u64, + column: sql.lines().last().map_or(0, |l| l.len() as u64) + 1, + } + } + }; + let prefix = format!("LINE {}: ", loc.line); + let sql_line = sql.split('\n').nth(loc.line as usize - 1).unwrap(); + let cursor = " ".repeat(prefix.len() + loc.column as usize - 1); + ParserError::ParserError(format!( + "{}\n{}{}\n{}^", + e.inner().to_string().replace('\n', ": "), + prefix, + sql_line, + cursor + )) + })?; + Ok(stmts) + } + + /// Parse object name from a string. + pub fn parse_object_name_str(s: &str) -> Result { + let mut tokenizer = Tokenizer::new(s); + let tokens = tokenizer.tokenize_with_location()?; + let parser = Parser(&tokens); + Parser::parse_object_name + .parse(parser) + .map_err(|e| ParserError::ParserError(e.inner().to_string())) + } + + /// Parse a list of semicolon-separated statements. + fn parse_statements(&mut self) -> PResult> { let mut stmts = Vec::new(); let mut expecting_statement_delimiter = false; loop { // ignore empty statements (between successive statement delimiters) - while parser.consume_token(&Token::SemiColon) { + while self.consume_token(&Token::SemiColon) { expecting_statement_delimiter = false; } - if parser.peek_token() == Token::EOF { + if self.peek_token() == Token::EOF { break; } if expecting_statement_delimiter { - return parser.expected("end of statement", parser.peek_token()); + return self.expected("end of statement"); } - let statement = parser.parse_statement()?; + let statement = self.parse_statement()?; stmts.push(statement); expecting_statement_delimiter = true; } @@ -231,14 +269,15 @@ impl Parser { /// Parse a single top-level statement (such as SELECT, INSERT, CREATE, etc.), /// stopping before the statement separator, if any. - pub fn parse_statement(&mut self) -> Result { + pub fn parse_statement(&mut self) -> PResult { + let checkpoint = *self; let token = self.next_token(); match token.token { Token::Word(w) => match w.keyword { Keyword::EXPLAIN => Ok(self.parse_explain()?), Keyword::ANALYZE => Ok(self.parse_analyze()?), Keyword::SELECT | Keyword::WITH | Keyword::VALUES => { - self.prev_token(); + *self = checkpoint; Ok(Statement::Query(Box::new(self.parse_query()?))) } Keyword::DECLARE => Ok(self.parse_declare()?), @@ -285,52 +324,28 @@ impl Parser { Keyword::FLUSH => Ok(Statement::Flush), Keyword::WAIT => Ok(Statement::Wait), Keyword::RECOVER => Ok(Statement::Recover), - _ => self.expected( - "an SQL statement", - Token::Word(w).with_location(token.location), - ), + _ => self.expected_at(checkpoint, "statement"), }, Token::LParen => { - self.prev_token(); + *self = checkpoint; Ok(Statement::Query(Box::new(self.parse_query()?))) } - unexpected => { - self.expected("an SQL statement", unexpected.with_location(token.location)) - } + _ => self.expected_at(checkpoint, "statement"), } } - pub fn parse_truncate(&mut self) -> Result { + pub fn parse_truncate(&mut self) -> PResult { let _ = self.parse_keyword(Keyword::TABLE); let table_name = self.parse_object_name()?; Ok(Statement::Truncate { table_name }) } - pub fn parse_analyze(&mut self) -> Result { + pub fn parse_analyze(&mut self) -> PResult { let table_name = self.parse_object_name()?; Ok(Statement::Analyze { table_name }) } - /// Check is enter array expression. - pub fn peek_array_depth(&self) -> usize { - self.array_depth - } - - /// When enter specify ARRAY prefix expression. - pub fn increase_array_depth(&mut self, num: usize) { - self.array_depth += num; - } - - /// When exit specify ARRAY prefix expression. - pub fn decrease_array_depth(&mut self, num: usize) { - self.array_depth -= num; - } - - pub fn is_in_array(&self) -> bool { - self.peek_array_depth() > 0 - } - /// Tries to parse a wildcard expression. If it is not a wildcard, parses an expression. /// /// A wildcard expression either means: @@ -339,14 +354,14 @@ impl Parser { /// contain parentheses. /// - Selecting all columns from a table. In this case, it is a /// [`WildcardOrExpr::QualifiedWildcard`] or a [`WildcardOrExpr::Wildcard`]. - pub fn parse_wildcard_or_expr(&mut self) -> Result { - let index = self.index; + pub fn parse_wildcard_or_expr(&mut self) -> PResult { + let checkpoint = *self; match self.next_token().token { Token::Word(w) if self.peek_token() == Token::Period => { // Since there's no parenthesis, `w` must be a column or a table // So what follows must be dot-delimited identifiers, e.g. `a.b.c.*` - let wildcard_expr = self.parse_simple_wildcard_expr(index)?; + let wildcard_expr = self.parse_simple_wildcard_expr(checkpoint)?; return self.word_concat_wildcard_expr(w.to_ident()?, wildcard_expr); } Token::Mul => { @@ -363,14 +378,14 @@ impl Parser { } // Now that we have an expr, what follows must be // dot-delimited identifiers, e.g. `b.c.*` in `(a).b.c.*` - let wildcard_expr = self.parse_simple_wildcard_expr(index)?; + let wildcard_expr = self.parse_simple_wildcard_expr(checkpoint)?; return self.expr_concat_wildcard_expr(expr, wildcard_expr); } } _ => (), }; - self.index = index; + *self = checkpoint; self.parse_expr().map(WildcardOrExpr::Expr) } @@ -379,7 +394,7 @@ impl Parser { &mut self, ident: Ident, simple_wildcard_expr: WildcardOrExpr, - ) -> Result { + ) -> PResult { let mut idents = vec![ident]; let mut except_cols = vec![]; match simple_wildcard_expr { @@ -412,7 +427,7 @@ impl Parser { &mut self, expr: Expr, simple_wildcard_expr: WildcardOrExpr, - ) -> Result { + ) -> PResult { if let WildcardOrExpr::Expr(e) = simple_wildcard_expr { return Ok(WildcardOrExpr::Expr(e)); } @@ -442,19 +457,13 @@ impl Parser { match simple_wildcard_expr { WildcardOrExpr::QualifiedWildcard(ids, except) => { if except.is_some() { - return self.expected( - "Expr quantified wildcard does not support except", - self.peek_token(), - ); + return self.expected("Expr quantified wildcard does not support except"); } idents.extend(ids.0); } WildcardOrExpr::Wildcard(except) => { if except.is_some() { - return self.expected( - "Expr quantified wildcard does not support except", - self.peek_token(), - ); + return self.expected("Expr quantified wildcard does not support except"); } } WildcardOrExpr::ExprQualifiedWildcard(_, _) => unreachable!(), @@ -466,12 +475,10 @@ impl Parser { /// Tries to parses a wildcard expression without any parentheses. /// /// If wildcard is not found, go back to `index` and parse an expression. - pub fn parse_simple_wildcard_expr( - &mut self, - index: usize, - ) -> Result { + pub fn parse_simple_wildcard_expr(&mut self, checkpoint: Self) -> PResult { let mut id_parts = vec![]; while self.consume_token(&Token::Period) { + let ckpt = *self; let token = self.next_token(); match token.token { Token::Word(w) => id_parts.push(w.to_ident()?), @@ -483,45 +490,40 @@ impl Parser { ObjectName(id_parts), self.parse_except()?, )) - } + }; } - unexpected => { - return self.expected( - "an identifier or a '*' after '.'", - unexpected.with_location(token.location), - ); + _ => { + *self = ckpt; + return self.expected("an identifier or a '*' after '.'"); } } } - self.index = index; + *self = checkpoint; self.parse_expr().map(WildcardOrExpr::Expr) } - pub fn parse_except(&mut self) -> Result>, ParserError> { + pub fn parse_except(&mut self) -> PResult>> { if !self.parse_keyword(Keyword::EXCEPT) { return Ok(None); } if !self.consume_token(&Token::LParen) { - return self.expected("EXCEPT should be followed by (", self.peek_token()); + return self.expected("EXCEPT should be followed by ("); } let exprs = self.parse_comma_separated(Parser::parse_expr)?; if self.consume_token(&Token::RParen) { Ok(Some(exprs)) } else { - self.expected( - "( should be followed by ) after column names", - self.peek_token(), - ) + self.expected("( should be followed by ) after column names") } } /// Parse a new expression - pub fn parse_expr(&mut self) -> Result { + pub fn parse_expr(&mut self) -> PResult { self.parse_subexpr(Precedence::Zero) } /// Parse tokens until the precedence changes - pub fn parse_subexpr(&mut self, precedence: Precedence) -> Result { + pub fn parse_subexpr(&mut self, precedence: Precedence) -> PResult { debug!("parsing expr, current token: {:?}", self.peek_token().token); let mut expr = self.parse_prefix()?; debug!("prefix: {:?}", expr); @@ -539,7 +541,7 @@ impl Parser { } /// Parse an expression prefix - pub fn parse_prefix(&mut self) -> Result { + pub fn parse_prefix(&mut self) -> PResult { // PostgreSQL allows any string literal to be preceded by a type name, indicating that the // string literal represents a literal of that type. Some examples: // @@ -574,11 +576,12 @@ impl Parser { } })); + let checkpoint = *self; let token = self.next_token(); let expr = match token.token.clone() { Token::Word(w) => match w.keyword { Keyword::TRUE | Keyword::FALSE | Keyword::NULL => { - self.prev_token(); + *self = checkpoint; Ok(Expr::Value(self.parse_value()?)) } Keyword::CASE => self.parse_case_expr(), @@ -596,10 +599,6 @@ impl Parser { expr: Box::new(self.parse_subexpr(Precedence::UnaryNot)?), }), Keyword::ROW => self.parse_row_expr(), - Keyword::ARRAY if self.peek_token() == Token::LBracket => { - self.expect_token(&Token::LBracket)?; - self.parse_array_expr(true) - } Keyword::ARRAY if self.peek_token() == Token::LParen => { // similar to `exists(subquery)` self.expect_token(&Token::LParen)?; @@ -607,6 +606,7 @@ impl Parser { self.expect_token(&Token::RParen)?; Ok(exists_node) } + Keyword::ARRAY if self.peek_token() == Token::LBracket => self.parse_array_expr(), // `LEFT` and `RIGHT` are reserved as identifier but okay as function Keyword::LEFT | Keyword::RIGHT => { self.parse_function(ObjectName(vec![w.to_ident()?])) @@ -627,7 +627,7 @@ impl Parser { // TODO: support `all/any/some(subquery)`. if let Expr::Subquery(_) = &sub { - parser_err!("ANY/SOME/ALL(Subquery) is not implemented")?; + parser_err!("ANY/SOME/ALL(Subquery) is not implemented"); } Ok(match keyword { @@ -638,7 +638,7 @@ impl Parser { }) } k if keywords::RESERVED_FOR_COLUMN_OR_TABLE_NAME.contains(&k) => { - parser_err!(format!("syntax error at or near {token}")) + parser_err!("syntax error at or near {token}") } // Here `w` is a word, check if it's a part of a multi-part // identifier, a function call, or a simple identifier: @@ -646,20 +646,18 @@ impl Parser { Token::LParen | Token::Period => { let mut id_parts: Vec = vec![w.to_ident()?]; while self.consume_token(&Token::Period) { + let ckpt = *self; let token = self.next_token(); match token.token { Token::Word(w) => id_parts.push(w.to_ident()?), - unexpected => { - return self.expected( - "an identifier or a '*' after '.'", - unexpected.with_location(token.location), - ); + _ => { + *self = ckpt; + return self.expected("an identifier or a '*' after '.'"); } } } - if self.consume_token(&Token::LParen) { - self.prev_token(); + if self.peek_token().token == Token::LParen { self.parse_function(ObjectName(id_parts)) } else { Ok(Expr::CompoundIdentifier(id_parts)) @@ -669,8 +667,6 @@ impl Parser { }, }, // End of Token::Word - Token::LBracket if self.is_in_array() => self.parse_array_expr(false), - tok @ Token::Minus | tok @ Token::Plus => { let op = if tok == Token::Plus { UnaryOperator::Plus @@ -715,7 +711,7 @@ impl Parser { | Token::NationalStringLiteral(_) | Token::HexStringLiteral(_) | Token::CstyleEscapesString(_) => { - self.prev_token(); + *self = checkpoint; Ok(Expr::Value(self.parse_value()?)) } Token::Parameter(number) => self.parse_param(number), @@ -729,18 +725,17 @@ impl Parser { }) } Token::LParen => { - let expr = - if self.parse_keyword(Keyword::SELECT) || self.parse_keyword(Keyword::WITH) { - self.prev_token(); - Expr::Subquery(Box::new(self.parse_query()?)) + let expr = if matches!(self.peek_token().token, Token::Word(w) if w.keyword == Keyword::SELECT || w.keyword == Keyword::WITH) + { + Expr::Subquery(Box::new(self.parse_query()?)) + } else { + let mut exprs = self.parse_comma_separated(Parser::parse_expr)?; + if exprs.len() == 1 { + Expr::Nested(Box::new(exprs.pop().unwrap())) } else { - let mut exprs = self.parse_comma_separated(Parser::parse_expr)?; - if exprs.len() == 1 { - Expr::Nested(Box::new(exprs.pop().unwrap())) - } else { - Expr::Row(exprs) - } - }; + Expr::Row(exprs) + } + }; self.expect_token(&Token::RParen)?; if self.peek_token() == Token::Period && matches!(expr, Expr::Nested(_)) { self.parse_struct_selection(expr) @@ -748,7 +743,7 @@ impl Parser { Ok(expr) } } - unexpected => self.expected("an expression:", unexpected.with_location(token.location)), + _ => self.expected_at(checkpoint, "an expression"), }?; if self.parse_keyword(Keyword::COLLATE) { @@ -761,16 +756,15 @@ impl Parser { } } - fn parse_param(&mut self, param: String) -> Result { - Ok(Expr::Parameter { - index: param.parse().map_err(|_| { - ParserError::ParserError(format!("Parameter symbol has a invalid index {}.", param)) - })?, - }) + fn parse_param(&mut self, param: String) -> PResult { + let Ok(index) = param.parse() else { + parser_err!("Parameter symbol has a invalid index {}.", param); + }; + Ok(Expr::Parameter { index }) } /// Parses a field selection expression. See also [`Expr::FieldIdentifier`]. - pub fn parse_struct_selection(&mut self, expr: Expr) -> Result { + pub fn parse_struct_selection(&mut self, expr: Expr) -> PResult { let mut nested_expr = expr; // Unwrap parentheses while let Expr::Nested(inner) = nested_expr { @@ -781,35 +775,21 @@ impl Parser { } /// Parses consecutive field identifiers after a period. i.e., `.foo.bar.baz` - pub fn parse_fields(&mut self) -> Result, ParserError> { - let mut idents = vec![]; - while self.consume_token(&Token::Period) { - let token = self.next_token(); - match token.token { - Token::Word(w) => { - idents.push(w.to_ident()?); - } - unexpected => { - return self.expected( - "an identifier after '.'", - unexpected.with_location(token.location), - ); - } - } - } - Ok(idents) + pub fn parse_fields(&mut self) -> PResult> { + repeat(.., preceded(Token::Period, cut_err(Self::parse_identifier))).parse_next(self) } - pub fn parse_qualified_operator(&mut self) -> Result { + pub fn parse_qualified_operator(&mut self) -> PResult { self.expect_token(&Token::LParen)?; + let checkpoint = *self; let schema = match self.parse_identifier_non_reserved() { Ok(ident) => { self.expect_token(&Token::Period)?; Some(ident) } Err(_) => { - self.prev_token(); + *self = checkpoint; None } }; @@ -825,11 +805,12 @@ impl Parser { // // To support custom operators and be fully compatible with PostgreSQL later, the // tokenizer should also be updated. + let checkpoint = *self; let token = self.next_token(); let name = token.token.to_string(); if !name.trim_matches(OP_CHARS).is_empty() { - self.prev_token(); - return self.expected(&format!("one of {}", OP_CHARS.iter().join(" ")), token); + return self + .expected_at(checkpoint, &format!("one of {}", OP_CHARS.iter().join(" "))); } name }; @@ -838,12 +819,12 @@ impl Parser { Ok(QualifiedOperator { schema, name }) } - pub fn parse_function(&mut self, name: ObjectName) -> Result { + pub fn parse_function(&mut self, name: ObjectName) -> PResult { self.expect_token(&Token::LParen)?; let distinct = self.parse_all_or_distinct()?; let (args, order_by, variadic) = self.parse_optional_args()?; let over = if self.parse_keyword(Keyword::OVER) { - // TBD: support window names (`OVER mywin`) in place of inline specification + // TODO: support window names (`OVER mywin`) in place of inline specification self.expect_token(&Token::LParen)?; let partition_by = if self.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) { // a list of possibly-qualified column names @@ -886,10 +867,7 @@ impl Parser { let within_group = if self.parse_keywords(&[Keyword::WITHIN, Keyword::GROUP]) { self.expect_token(&Token::LParen)?; self.expect_keywords(&[Keyword::ORDER, Keyword::BY])?; - let order_by_parsed = self.parse_comma_separated(Parser::parse_order_by_expr)?; - let order_by = order_by_parsed.iter().exactly_one().map_err(|_| { - ParserError::ParserError("only one arg in order by is expected here".to_string()) - })?; + let order_by = self.parse_order_by_expr()?; self.expect_token(&Token::RParen)?; Some(Box::new(order_by.clone())) } else { @@ -908,34 +886,35 @@ impl Parser { })) } - pub fn parse_window_frame_units(&mut self) -> Result { - let token = self.next_token(); - match token.token { - Token::Word(w) => match w.keyword { - Keyword::ROWS => Ok(WindowFrameUnits::Rows), - Keyword::RANGE => Ok(WindowFrameUnits::Range), - Keyword::GROUPS => Ok(WindowFrameUnits::Groups), - _ => self.expected( - "ROWS, RANGE, GROUPS", - Token::Word(w).with_location(token.location), - )?, - }, - unexpected => self.expected( - "ROWS, RANGE, GROUPS", - unexpected.with_location(token.location), - ), + pub fn parse_window_frame_units(&mut self) -> PResult { + dispatch! { peek(keyword); + Keyword::ROWS => keyword.value(WindowFrameUnits::Rows), + Keyword::RANGE => keyword.value(WindowFrameUnits::Range), + Keyword::GROUPS => keyword.value(WindowFrameUnits::Groups), + Keyword::SESSION => keyword.value(WindowFrameUnits::Session), + _ => fail, } + .expect("ROWS, RANGE, or GROUPS") + .parse_next(self) } - pub fn parse_window_frame(&mut self) -> Result { + pub fn parse_window_frame(&mut self) -> PResult { let units = self.parse_window_frame_units()?; - let (start_bound, end_bound) = if self.parse_keyword(Keyword::BETWEEN) { - let start_bound = self.parse_window_frame_bound()?; + let bounds = if self.parse_keyword(Keyword::BETWEEN) { + // `BETWEEN AND ` + let start = self.parse_window_frame_bound()?; self.expect_keyword(Keyword::AND)?; - let end_bound = Some(self.parse_window_frame_bound()?); - (start_bound, end_bound) + let end = Some(self.parse_window_frame_bound()?); + WindowFrameBounds::Bounds { start, end } + } else if self.parse_keywords(&[Keyword::WITH, Keyword::GAP]) { + // `WITH GAP `, only for session frames + WindowFrameBounds::Gap(Box::new(self.parse_expr()?)) } else { - (self.parse_window_frame_bound()?, None) + // `` + WindowFrameBounds::Bounds { + start: self.parse_window_frame_bound()?, + end: None, + } }; let exclusion = if self.parse_keyword(Keyword::EXCLUDE) { Some(self.parse_window_frame_exclusion()?) @@ -944,14 +923,13 @@ impl Parser { }; Ok(WindowFrame { units, - start_bound, - end_bound, + bounds, exclusion, }) } /// Parse `CURRENT ROW` or `{ | UNBOUNDED } { PRECEDING | FOLLOWING }` - pub fn parse_window_frame_bound(&mut self) -> Result { + pub fn parse_window_frame_bound(&mut self) -> PResult { if self.parse_keywords(&[Keyword::CURRENT, Keyword::ROW]) { Ok(WindowFrameBound::CurrentRow) } else { @@ -965,12 +943,12 @@ impl Parser { } else if self.parse_keyword(Keyword::FOLLOWING) { Ok(WindowFrameBound::Following(rows)) } else { - self.expected("PRECEDING or FOLLOWING", self.peek_token()) + self.expected("PRECEDING or FOLLOWING") } } } - pub fn parse_window_frame_exclusion(&mut self) -> Result { + pub fn parse_window_frame_exclusion(&mut self) -> PResult { if self.parse_keywords(&[Keyword::CURRENT, Keyword::ROW]) { Ok(WindowFrameExclusion::CurrentRow) } else if self.parse_keyword(Keyword::GROUP) { @@ -980,13 +958,13 @@ impl Parser { } else if self.parse_keywords(&[Keyword::NO, Keyword::OTHERS]) { Ok(WindowFrameExclusion::NoOthers) } else { - self.expected("CURRENT ROW, GROUP, TIES, or NO OTHERS", self.peek_token()) + self.expected("CURRENT ROW, GROUP, TIES, or NO OTHERS") } } /// parse a group by expr. a group by expr can be one of group sets, roll up, cube, or simple /// expr. - fn parse_group_by_expr(&mut self) -> Result { + fn parse_group_by_expr(&mut self) -> PResult { if self.parse_keywords(&[Keyword::GROUPING, Keyword::SETS]) { self.expect_token(&Token::LParen)?; let result = self.parse_comma_separated(|p| p.parse_tuple(true, true))?; @@ -1010,11 +988,7 @@ impl Parser { /// parse a tuple with `(` and `)`. /// If `lift_singleton` is true, then a singleton tuple is lifted to a tuple of length 1, /// otherwise it will fail. If `allow_empty` is true, then an empty tuple is allowed. - fn parse_tuple( - &mut self, - lift_singleton: bool, - allow_empty: bool, - ) -> Result, ParserError> { + fn parse_tuple(&mut self, lift_singleton: bool, allow_empty: bool) -> PResult> { if lift_singleton { if self.consume_token(&Token::LParen) { let result = if allow_empty && self.consume_token(&Token::RParen) { @@ -1041,153 +1015,49 @@ impl Parser { } } - pub fn parse_case_expr(&mut self) -> Result { - let mut operand = None; - if !self.parse_keyword(Keyword::WHEN) { - operand = Some(Box::new(self.parse_expr()?)); - self.expect_keyword(Keyword::WHEN)?; - } - let mut conditions = vec![]; - let mut results = vec![]; - loop { - conditions.push(self.parse_expr()?); - self.expect_keyword(Keyword::THEN)?; - results.push(self.parse_expr()?); - if !self.parse_keyword(Keyword::WHEN) { - break; - } - } - let else_result = if self.parse_keyword(Keyword::ELSE) { - Some(Box::new(self.parse_expr()?)) - } else { - None - }; - self.expect_keyword(Keyword::END)?; - Ok(Expr::Case { - operand, - conditions, - results, - else_result, - }) + pub fn parse_case_expr(&mut self) -> PResult { + parser_v2::expr_case(self) } /// Parse a SQL CAST function e.g. `CAST(expr AS FLOAT)` - pub fn parse_cast_expr(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - self.expect_keyword(Keyword::AS)?; - let data_type = self.parse_data_type()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::Cast { - expr: Box::new(expr), - data_type, - }) + pub fn parse_cast_expr(&mut self) -> PResult { + parser_v2::expr_cast(self) } /// Parse a SQL TRY_CAST function e.g. `TRY_CAST(expr AS FLOAT)` - pub fn parse_try_cast_expr(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - self.expect_keyword(Keyword::AS)?; - let data_type = self.parse_data_type()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::TryCast { - expr: Box::new(expr), - data_type, - }) + pub fn parse_try_cast_expr(&mut self) -> PResult { + parser_v2::expr_try_cast(self) } /// Parse a SQL EXISTS expression e.g. `WHERE EXISTS(SELECT ...)`. - pub fn parse_exists_expr(&mut self) -> Result { + pub fn parse_exists_expr(&mut self) -> PResult { self.expect_token(&Token::LParen)?; let exists_node = Expr::Exists(Box::new(self.parse_query()?)); self.expect_token(&Token::RParen)?; Ok(exists_node) } - pub fn parse_extract_expr(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - let field = self.parse_date_time_field_in_extract()?; - self.expect_keyword(Keyword::FROM)?; - let expr = self.parse_expr()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::Extract { - field, - expr: Box::new(expr), - }) + pub fn parse_extract_expr(&mut self) -> PResult { + parser_v2::expr_extract(self) } - pub fn parse_substring_expr(&mut self) -> Result { - // PARSE SUBSTRING (EXPR [FROM 1] [FOR 3]) - self.expect_token(&Token::LParen)?; - let expr = self.parse_expr()?; - let mut from_expr = None; - if self.parse_keyword(Keyword::FROM) || self.consume_token(&Token::Comma) { - from_expr = Some(self.parse_expr()?); - } - - let mut to_expr = None; - if self.parse_keyword(Keyword::FOR) || self.consume_token(&Token::Comma) { - to_expr = Some(self.parse_expr()?); - } - self.expect_token(&Token::RParen)?; - - Ok(Expr::Substring { - expr: Box::new(expr), - substring_from: from_expr.map(Box::new), - substring_for: to_expr.map(Box::new), - }) + pub fn parse_substring_expr(&mut self) -> PResult { + parser_v2::expr_substring(self) } /// `POSITION( IN )` - pub fn parse_position_expr(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - - // Logically `parse_expr`, but limited to those with precedence higher than `BETWEEN`/`IN`, - // to avoid conflict with general IN operator, for example `position(a IN (b) IN (c))`. - // https://github.com/postgres/postgres/blob/REL_15_2/src/backend/parser/gram.y#L16012 - let substring = self.parse_subexpr(Precedence::Between)?; - self.expect_keyword(Keyword::IN)?; - let string = self.parse_subexpr(Precedence::Between)?; - - self.expect_token(&Token::RParen)?; - - Ok(Expr::Position { - substring: Box::new(substring), - string: Box::new(string), - }) + pub fn parse_position_expr(&mut self) -> PResult { + parser_v2::expr_position(self) } /// `OVERLAY( PLACING FROM [ FOR ])` - pub fn parse_overlay_expr(&mut self) -> Result { - self.expect_token(&Token::LParen)?; - - let expr = self.parse_expr()?; - - self.expect_keyword(Keyword::PLACING)?; - let new_substring = self.parse_expr()?; - - self.expect_keyword(Keyword::FROM)?; - let start = self.parse_expr()?; - - let mut count = None; - if self.parse_keyword(Keyword::FOR) { - count = Some(self.parse_expr()?); - } - - self.expect_token(&Token::RParen)?; - - Ok(Expr::Overlay { - expr: Box::new(expr), - new_substring: Box::new(new_substring), - start: Box::new(start), - count: count.map(Box::new), - }) + pub fn parse_overlay_expr(&mut self) -> PResult { + parser_v2::expr_overlay(self) } /// `TRIM ([WHERE] ['text'] FROM 'text')`\ /// `TRIM ([WHERE] [FROM] 'text' [, 'text'])` - pub fn parse_trim_expr(&mut self) -> Result { + pub fn parse_trim_expr(&mut self) -> PResult { self.expect_token(&Token::LParen)?; let mut trim_where = None; if let Token::Word(word) = self.peek_token().token { @@ -1223,87 +1093,77 @@ impl Parser { }) } - pub fn parse_trim_where(&mut self) -> Result { - let token = self.next_token(); - match token.token { - Token::Word(w) => match w.keyword { - Keyword::BOTH => Ok(TrimWhereField::Both), - Keyword::LEADING => Ok(TrimWhereField::Leading), - Keyword::TRAILING => Ok(TrimWhereField::Trailing), - _ => self.expected( - "trim_where field", - Token::Word(w).with_location(token.location), - )?, - }, - unexpected => { - self.expected("trim_where field", unexpected.with_location(token.location)) - } + pub fn parse_trim_where(&mut self) -> PResult { + dispatch! { peek(keyword); + Keyword::BOTH => keyword.value(TrimWhereField::Both), + Keyword::LEADING => keyword.value(TrimWhereField::Leading), + Keyword::TRAILING => keyword.value(TrimWhereField::Trailing), + _ => fail } + .expect("BOTH, LEADING, or TRAILING") + .parse_next(self) } /// Parses an array expression `[ex1, ex2, ..]` - /// if `named` is `true`, came from an expression like `ARRAY[ex1, ex2]` - pub fn parse_array_expr(&mut self, named: bool) -> Result { - self.increase_array_depth(1); - if self.array_named_stack.len() < self.peek_array_depth() { - self.array_named_stack.push(named); - } else if let Err(parse_err) = self.check_same_named_array(named) { - Err(parse_err)? - } + pub fn parse_array_expr(&mut self) -> PResult { + let mut expected_depth = None; + let exprs = self.parse_array_inner(0, &mut expected_depth)?; + Ok(Expr::Array(Array { + elem: exprs, + // Top-level array is named. + named: true, + })) + } - if self.peek_token() == Token::RBracket { - let _ = self.next_token(); // consume ] - self.decrease_array_depth(1); - Ok(Expr::Array(Array { - elem: vec![], - named, - })) + fn parse_array_inner( + &mut self, + depth: usize, + expected_depth: &mut Option, + ) -> PResult> { + self.expect_token(&Token::LBracket)?; + if let Some(expected_depth) = *expected_depth + && depth > expected_depth + { + return self.expected("]"); + } + let exprs = if self.peek_token() == Token::LBracket { + self.parse_comma_separated(|parser| { + let exprs = parser.parse_array_inner(depth + 1, expected_depth)?; + Ok(Expr::Array(Array { + elem: exprs, + named: false, + })) + })? } else { - let exprs = self.parse_comma_separated(Parser::parse_expr)?; - self.expect_token(&Token::RBracket)?; - if self.array_named_stack.len() > self.peek_array_depth() { - self.array_named_stack.pop(); + if let Some(expected_depth) = *expected_depth { + if depth < expected_depth { + return self.expected("["); + } + } else { + *expected_depth = Some(depth); } - self.decrease_array_depth(1); - Ok(Expr::Array(Array { elem: exprs, named })) - } - } - - fn check_same_named_array(&mut self, current_named: bool) -> Result<(), ParserError> { - let previous_named = self.array_named_stack.last().unwrap(); - if current_named != *previous_named { - // for '[' - self.prev_token(); - if current_named { - // for keyword 'array' - self.prev_token(); + if self.consume_token(&Token::RBracket) { + return Ok(vec![]); } - parser_err!(format!("syntax error at or near {}", self.peek_token()))? - } else { - Ok(()) - } + self.parse_comma_separated(Self::parse_expr)? + }; + self.expect_token(&Token::RBracket)?; + Ok(exprs) } // This function parses date/time fields for interval qualifiers. - pub fn parse_date_time_field(&mut self) -> Result { - let token = self.next_token(); - match token.token { - Token::Word(w) => match w.keyword { - Keyword::YEAR => Ok(DateTimeField::Year), - Keyword::MONTH => Ok(DateTimeField::Month), - Keyword::DAY => Ok(DateTimeField::Day), - Keyword::HOUR => Ok(DateTimeField::Hour), - Keyword::MINUTE => Ok(DateTimeField::Minute), - Keyword::SECOND => Ok(DateTimeField::Second), - _ => self.expected( - "date/time field", - Token::Word(w).with_location(token.location), - )?, - }, - unexpected => { - self.expected("date/time field", unexpected.with_location(token.location)) - } + pub fn parse_date_time_field(&mut self) -> PResult { + dispatch! { peek(keyword); + Keyword::YEAR => keyword.value(DateTimeField::Year), + Keyword::MONTH => keyword.value(DateTimeField::Month), + Keyword::DAY => keyword.value(DateTimeField::Day), + Keyword::HOUR => keyword.value(DateTimeField::Hour), + Keyword::MINUTE => keyword.value(DateTimeField::Minute), + Keyword::SECOND => keyword.value(DateTimeField::Second), + _ => fail, } + .expect("date/time field") + .parse_next(self) } // This function parses date/time fields for the EXTRACT function-like operator. PostgreSQL @@ -1315,13 +1175,15 @@ impl Parser { // select extract("invaLId" from null::date); // select extract('invaLId' from null::date); // ``` - pub fn parse_date_time_field_in_extract(&mut self) -> Result { + pub fn parse_date_time_field_in_extract(&mut self) -> PResult { + let checkpoint = *self; let token = self.next_token(); match token.token { Token::Word(w) => Ok(w.value.to_uppercase()), Token::SingleQuotedString(s) => Ok(s.to_uppercase()), - unexpected => { - self.expected("date/time field", unexpected.with_location(token.location)) + _ => { + *self = checkpoint; + self.expected("date/time field") } } } @@ -1338,7 +1200,7 @@ impl Parser { /// 6. `INTERVAL '1:1' HOUR (5) TO MINUTE (5)` /// /// Note that we do not currently attempt to parse the quoted value. - pub fn parse_literal_interval(&mut self) -> Result { + pub fn parse_literal_interval(&mut self) -> PResult { // The SQL standard allows an optional sign before the value string, but // it is not clear if any implementations support that syntax, so we // don't currently try to parse it. (The sign can instead be included @@ -1406,7 +1268,8 @@ impl Parser { } /// Parse an operator following an expression - pub fn parse_infix(&mut self, expr: Expr, precedence: Precedence) -> Result { + pub fn parse_infix(&mut self, expr: Expr, precedence: Precedence) -> PResult { + let checkpoint = *self; let tok = self.next_token(); debug!("parsing infix {:?}", tok.token); let regular_binary_operator = match &tok.token { @@ -1476,7 +1339,7 @@ impl Parser { // // TODO: support `all/any/some(subquery)`. // if let Expr::Subquery(_) = &right { - // parser_err!("ANY/SOME/ALL(Subquery) is not implemented")?; + // parser_err!("ANY/SOME/ALL(Subquery) is not implemented"); // } // let right = match keyword { @@ -1532,27 +1395,23 @@ impl Parser { } else { self.expected( "[NOT] { TRUE | FALSE | UNKNOWN | NULL | DISTINCT FROM | JSON } after IS", - self.peek_token(), ) } } } Keyword::AT => { - if self.parse_keywords(&[Keyword::TIME, Keyword::ZONE]) { - let token = self.next_token(); - match token.token { - Token::SingleQuotedString(time_zone) => Ok(Expr::AtTimeZone { - timestamp: Box::new(expr), - time_zone, - }), - unexpected => self.expected( - "Expected Token::SingleQuotedString after AT TIME ZONE", - unexpected.with_location(token.location), - ), - } - } else { - self.expected("Expected Token::Word after AT", tok) - } + assert_eq!(precedence, Precedence::At); + let time_zone = Box::new( + preceded( + (Keyword::TIME, Keyword::ZONE), + cut_err(|p: &mut Self| p.parse_subexpr(precedence)), + ) + .parse_next(self)?, + ); + Ok(Expr::AtTimeZone { + timestamp: Box::new(expr), + time_zone, + }) } keyword @ (Keyword::ALL | Keyword::ANY | Keyword::SOME) => { self.expect_token(&Token::LParen)?; @@ -1563,7 +1422,7 @@ impl Parser { // TODO: support `all/any/some(subquery)`. if let Expr::Subquery(_) = &sub { - parser_err!("ANY/SOME/ALL(Subquery) is not implemented")?; + parser_err!("ANY/SOME/ALL(Subquery) is not implemented"); } Ok(match keyword { @@ -1579,7 +1438,7 @@ impl Parser { | Keyword::LIKE | Keyword::ILIKE | Keyword::SIMILAR => { - self.prev_token(); + *self = checkpoint; let negated = self.parse_keyword(Keyword::NOT); if self.parse_keyword(Keyword::IN) { self.parse_in(expr, negated) @@ -1607,11 +1466,11 @@ impl Parser { escape_char: self.parse_escape()?, }) } else { - self.expected("IN, BETWEEN or SIMILAR TO after NOT", self.peek_token()) + self.expected("IN, BETWEEN or SIMILAR TO after NOT") } } // Can only happen if `get_next_precedence` got out of sync with this function - _ => parser_err!(format!("No infix parser for token {:?}", tok)), + _ => parser_err!("No infix parser for token {:?}", tok), } } else if Token::DoubleColon == tok { self.parse_pg_cast(expr) @@ -1625,20 +1484,18 @@ impl Parser { self.parse_array_index(expr) } else { // Can only happen if `get_next_precedence` got out of sync with this function - parser_err!(format!("No infix parser for token {:?}", tok)) + parser_err!("No infix parser for token {:?}", tok) } } /// parse the ESCAPE CHAR portion of LIKE, ILIKE, and SIMILAR TO - pub fn parse_escape(&mut self) -> Result, ParserError> { + pub fn parse_escape(&mut self) -> PResult> { if self.parse_keyword(Keyword::ESCAPE) { let s = self.parse_literal_string()?; let mut chs = s.chars(); if let Some(ch) = chs.next() { if chs.next().is_some() { - parser_err!(format!( - "Escape string must be empty or one character, found {s:?}" - )) + parser_err!("Escape string must be empty or one character, found {s:?}") } else { Ok(Some(EscapeChar::escape(ch))) } @@ -1652,7 +1509,7 @@ impl Parser { /// We parse both `array[1,9][1]`, `array[1,9][1:2]`, `array[1,9][:2]`, `array[1,9][1:]` and /// `array[1,9][:]` in this function. - pub fn parse_array_index(&mut self, expr: Expr) -> Result { + pub fn parse_array_index(&mut self, expr: Expr) -> PResult { let new_expr = match self.peek_token().token { Token::Colon => { // [:] or [:N] @@ -1717,7 +1574,7 @@ impl Parser { } /// Parses the optional constraints following the `IS [NOT] JSON` predicate - pub fn parse_is_json(&mut self, expr: Expr, negated: bool) -> Result { + pub fn parse_is_json(&mut self, expr: Expr, negated: bool) -> PResult { let item_type = match self.peek_token().token { Token::Word(w) => match w.keyword { Keyword::VALUE => Some(JsonPredicateType::Value), @@ -1749,10 +1606,10 @@ impl Parser { } /// Parses the parens following the `[ NOT ] IN` operator - pub fn parse_in(&mut self, expr: Expr, negated: bool) -> Result { + pub fn parse_in(&mut self, expr: Expr, negated: bool) -> PResult { self.expect_token(&Token::LParen)?; - let in_op = if self.parse_keyword(Keyword::SELECT) || self.parse_keyword(Keyword::WITH) { - self.prev_token(); + let in_op = if matches!(self.peek_token().token, Token::Word(w) if w.keyword == Keyword::SELECT || w.keyword == Keyword::WITH) + { Expr::InSubquery { expr: Box::new(expr), subquery: Box::new(self.parse_query()?), @@ -1770,7 +1627,7 @@ impl Parser { } /// Parses `BETWEEN AND `, assuming the `BETWEEN` keyword was already consumed - pub fn parse_between(&mut self, expr: Expr, negated: bool) -> Result { + pub fn parse_between(&mut self, expr: Expr, negated: bool) -> PResult { // Stop parsing subexpressions for and on tokens with // precedence lower than that of `BETWEEN`, such as `AND`, `IS`, etc. let low = self.parse_subexpr(Precedence::Between)?; @@ -1785,7 +1642,7 @@ impl Parser { } /// Parse a postgresql casting style which is in the form of `expr::datatype` - pub fn parse_pg_cast(&mut self, expr: Expr) -> Result { + pub fn parse_pg_cast(&mut self, expr: Expr) -> PResult { Ok(Expr::Cast { expr: Box::new(expr), data_type: self.parse_data_type()?, @@ -1793,7 +1650,7 @@ impl Parser { } /// Get the precedence of the next token - pub fn get_next_precedence(&self) -> Result { + pub fn get_next_precedence(&self) -> PResult { use Precedence as P; let token = self.peek_token(); @@ -1807,7 +1664,7 @@ impl Parser { (Token::Word(w), Token::Word(w2)) if w.keyword == Keyword::TIME && w2.keyword == Keyword::ZONE => { - Ok(P::Other) + Ok(P::At) } _ => Ok(P::Zero), } @@ -1898,17 +1755,15 @@ impl Parser { /// Return nth non-whitespace token that has not yet been processed pub fn peek_nth_token(&self, mut n: usize) -> TokenWithLocation { - let mut index = self.index; + let mut index = 0; loop { + let token = self.0.get(index); index += 1; - let token = self.tokens.get(index - 1); match token.map(|x| &x.token) { Some(Token::Whitespace(_)) => continue, _ => { if n == 0 { - return token - .cloned() - .unwrap_or(TokenWithLocation::wrap(Token::EOF)); + return token.cloned().unwrap_or(TokenWithLocation::eof()); } n -= 1; } @@ -1921,61 +1776,37 @@ impl Parser { /// repeatedly after reaching EOF. pub fn next_token(&mut self) -> TokenWithLocation { loop { - self.index += 1; - let token = self.tokens.get(self.index - 1); - match token.map(|x| &x.token) { - Some(Token::Whitespace(_)) => continue, - _ => { - return token - .cloned() - .unwrap_or(TokenWithLocation::wrap(Token::EOF)) - } + let Some(token) = self.0.first() else { + return TokenWithLocation::eof(); + }; + self.0 = &self.0[1..]; + match token.token { + Token::Whitespace(_) => continue, + _ => return token.clone(), } } } /// Return the first unprocessed token, possibly whitespace. pub fn next_token_no_skip(&mut self) -> Option<&TokenWithLocation> { - self.index += 1; - self.tokens.get(self.index - 1) + if self.0.is_empty() { + None + } else { + let (first, rest) = self.0.split_at(1); + self.0 = rest; + Some(&first[0]) + } } - /// Push back the last one non-whitespace token. Must be called after - /// `next_token()`, otherwise might panic. OK to call after - /// `next_token()` indicates an EOF. - pub fn prev_token(&mut self) { - loop { - assert!(self.index > 0); - self.index -= 1; - if let Some(token) = self.tokens.get(self.index) - && let Token::Whitespace(_) = token.token - { - continue; - } - return; - } + /// Report an expected error at the current position. + pub fn expected(&self, expected: &str) -> PResult { + parser_err!("expected {}, found: {}", expected, self.peek_token().token) } - /// Report unexpected token - pub fn expected(&self, expected: &str, found: TokenWithLocation) -> Result { - let start_off = self.index.saturating_sub(10); - let end_off = self.index.min(self.tokens.len()); - let near_tokens = &self.tokens[start_off..end_off]; - struct TokensDisplay<'a>(&'a [TokenWithLocation]); - impl<'a> fmt::Display for TokensDisplay<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for token in self.0 { - write!(f, "{}", token.token)?; - } - Ok(()) - } - } - parser_err!(format!( - "Expected {}, found: {}\nNear \"{}\"", - expected, - found, - TokensDisplay(near_tokens), - )) + /// Revert the parser to a previous position and report an expected error. + pub fn expected_at(&mut self, checkpoint: Self, expected: &str) -> PResult { + *self = checkpoint; + self.expected(expected) } /// Look for an expected keyword and consume it if it exists @@ -1993,12 +1824,12 @@ impl Parser { /// Look for an expected sequence of keywords and consume them if they exist #[must_use] pub fn parse_keywords(&mut self, keywords: &[Keyword]) -> bool { - let index = self.index; + let checkpoint = *self; for &keyword in keywords { if !self.parse_keyword(keyword) { // println!("parse_keywords aborting .. did not find {:?}", keyword); // reset index and return immediately - self.index = index; + *self = checkpoint; return false; } } @@ -2030,30 +1861,27 @@ impl Parser { } /// Bail out if the current token is not one of the expected keywords, or consume it if it is - pub fn expect_one_of_keywords(&mut self, keywords: &[Keyword]) -> Result { + pub fn expect_one_of_keywords(&mut self, keywords: &[Keyword]) -> PResult { if let Some(keyword) = self.parse_one_of_keywords(keywords) { Ok(keyword) } else { let keywords: Vec = keywords.iter().map(|x| format!("{:?}", x)).collect(); - self.expected( - &format!("one of {}", keywords.join(" or ")), - self.peek_token(), - ) + self.expected(&format!("one of {}", keywords.join(" or "))) } } /// Bail out if the current token is not an expected keyword, or consume it if it is - pub fn expect_keyword(&mut self, expected: Keyword) -> Result<(), ParserError> { + pub fn expect_keyword(&mut self, expected: Keyword) -> PResult<()> { if self.parse_keyword(expected) { Ok(()) } else { - self.expected(format!("{:?}", &expected).as_str(), self.peek_token()) + self.expected(format!("{:?}", &expected).as_str()) } } /// Bail out if the following tokens are not the expected sequence of /// keywords, or consume them if they are. - pub fn expect_keywords(&mut self, expected: &[Keyword]) -> Result<(), ParserError> { + pub fn expect_keywords(&mut self, expected: &[Keyword]) -> PResult<()> { for &kw in expected { self.expect_keyword(kw)?; } @@ -2072,18 +1900,18 @@ impl Parser { } /// Bail out if the current token is not an expected keyword, or consume it if it is - pub fn expect_token(&mut self, expected: &Token) -> Result<(), ParserError> { + pub fn expect_token(&mut self, expected: &Token) -> PResult<()> { if self.consume_token(expected) { Ok(()) } else { - self.expected(&expected.to_string(), self.peek_token()) + self.expected(&expected.to_string()) } } /// Parse a comma-separated list of 1+ items accepted by `F` - pub fn parse_comma_separated(&mut self, mut f: F) -> Result, ParserError> + pub fn parse_comma_separated(&mut self, mut f: F) -> PResult> where - F: FnMut(&mut Parser) -> Result, + F: FnMut(&mut Self) -> PResult, { let mut values = vec![]; loop { @@ -2100,31 +1928,31 @@ impl Parser { #[must_use] fn maybe_parse(&mut self, mut f: F) -> Option where - F: FnMut(&mut Parser) -> Result, + F: FnMut(&mut Self) -> PResult, { - let index = self.index; + let checkpoint = *self; if let Ok(t) = f(self) { Some(t) } else { - self.index = index; + *self = checkpoint; None } } /// Parse either `ALL` or `DISTINCT`. Returns `true` if `DISTINCT` is parsed and results in a /// `ParserError` if both `ALL` and `DISTINCT` are fround. - pub fn parse_all_or_distinct(&mut self) -> Result { + pub fn parse_all_or_distinct(&mut self) -> PResult { let all = self.parse_keyword(Keyword::ALL); let distinct = self.parse_keyword(Keyword::DISTINCT); if all && distinct { - parser_err!("Cannot specify both ALL and DISTINCT".to_string()) + parser_err!("Cannot specify both ALL and DISTINCT") } else { Ok(distinct) } } /// Parse either `ALL` or `DISTINCT` or `DISTINCT ON ()`. - pub fn parse_all_or_distinct_on(&mut self) -> Result { + pub fn parse_all_or_distinct_on(&mut self) -> PResult { if self.parse_keywords(&[Keyword::DISTINCT, Keyword::ON]) { self.expect_token(&Token::LParen)?; let exprs = self.parse_comma_separated(Parser::parse_expr)?; @@ -2138,7 +1966,7 @@ impl Parser { } /// Parse a SQL CREATE statement - pub fn parse_create(&mut self) -> Result { + pub fn parse_create(&mut self) -> PResult { let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]); let temporary = self .parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY]) @@ -2166,7 +1994,6 @@ impl Parser { } else if or_replace { self.expected( "[EXTERNAL] TABLE or [MATERIALIZED] VIEW or [MATERIALIZED] SOURCE or SINK or FUNCTION after CREATE OR REPLACE", - self.peek_token(), ) } else if self.parse_keyword(Keyword::INDEX) { self.parse_create_index(false) @@ -2178,21 +2005,35 @@ impl Parser { self.parse_create_database() } else if self.parse_keyword(Keyword::USER) { self.parse_create_user() + } else if self.parse_keyword(Keyword::SECRET) { + self.parse_create_secret() } else { - self.expected("an object type after CREATE", self.peek_token()) + self.expected("an object type after CREATE") } } - pub fn parse_create_schema(&mut self) -> Result { + pub fn parse_create_schema(&mut self) -> PResult { let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); - let schema_name = self.parse_object_name()?; + let (schema_name, user_specified) = if self.parse_keyword(Keyword::AUTHORIZATION) { + let user_specified = self.parse_object_name()?; + (user_specified.clone(), Some(user_specified)) + } else { + let schema_name = self.parse_object_name()?; + let user_specified = if self.parse_keyword(Keyword::AUTHORIZATION) { + Some(self.parse_object_name()?) + } else { + None + }; + (schema_name, user_specified) + }; Ok(Statement::CreateSchema { schema_name, if_not_exists, + user_specified, }) } - pub fn parse_create_database(&mut self) -> Result { + pub fn parse_create_database(&mut self) -> PResult { let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let db_name = self.parse_object_name()?; Ok(Statement::CreateDatabase { @@ -2205,7 +2046,7 @@ impl Parser { &mut self, materialized: bool, or_replace: bool, - ) -> Result { + ) -> PResult { let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); // Many dialects support `OR ALTER` right after `CREATE`, but we don't (yet). // ANSI SQL and Postgres support RECURSIVE here, but we don't support it either. @@ -2240,7 +2081,7 @@ impl Parser { // [WITH (properties)]? // ROW FORMAT // [ROW SCHEMA LOCATION ]? - pub fn parse_create_source(&mut self, _or_replace: bool) -> Result { + pub fn parse_create_source(&mut self, _or_replace: bool) -> PResult { Ok(Statement::CreateSource { stmt: CreateSourceStatement::parse_to(self)?, }) @@ -2253,7 +2094,7 @@ impl Parser { // FROM // // [WITH (properties)]? - pub fn parse_create_sink(&mut self, _or_replace: bool) -> Result { + pub fn parse_create_sink(&mut self, _or_replace: bool) -> PResult { Ok(Statement::CreateSink { stmt: CreateSinkStatement::parse_to(self)?, }) @@ -2266,10 +2107,7 @@ impl Parser { // FROM // // [WITH (properties)]? - pub fn parse_create_subscription( - &mut self, - _or_replace: bool, - ) -> Result { + pub fn parse_create_subscription(&mut self, _or_replace: bool) -> PResult { Ok(Statement::CreateSubscription { stmt: CreateSubscriptionStatement::parse_to(self)?, }) @@ -2280,7 +2118,7 @@ impl Parser { // [IF NOT EXISTS]? // // [WITH (properties)]? - pub fn parse_create_connection(&mut self) -> Result { + pub fn parse_create_connection(&mut self) -> PResult { Ok(Statement::CreateConnection { stmt: CreateConnectionStatement::parse_to(self)?, }) @@ -2290,11 +2128,10 @@ impl Parser { &mut self, or_replace: bool, temporary: bool, - ) -> Result { + ) -> PResult { let name = self.parse_object_name()?; self.expect_token(&Token::LParen)?; - let args = if self.consume_token(&Token::RParen) { - self.prev_token(); + let args = if self.peek_token().token == Token::RParen { None } else { Some(self.parse_comma_separated(Parser::parse_function_arg)?) @@ -2313,7 +2150,7 @@ impl Parser { // allow a trailing comma, even though it's not in standard break; } else if !comma { - return self.expected("',' or ')'", self.peek_token()); + return self.expected("',' or ')'"); } } Some(CreateFunctionReturns::Table(values)) @@ -2338,17 +2175,14 @@ impl Parser { }) } - fn parse_create_aggregate(&mut self, or_replace: bool) -> Result { + fn parse_create_aggregate(&mut self, or_replace: bool) -> PResult { let name = self.parse_object_name()?; self.expect_token(&Token::LParen)?; let args = self.parse_comma_separated(Parser::parse_function_arg)?; self.expect_token(&Token::RParen)?; - let return_type = if self.parse_keyword(Keyword::RETURNS) { - Some(self.parse_data_type()?) - } else { - None - }; + self.expect_keyword(Keyword::RETURNS)?; + let returns = self.parse_data_type()?; let append_only = self.parse_keywords(&[Keyword::APPEND, Keyword::ONLY]); let params = self.parse_create_function_body()?; @@ -2357,38 +2191,38 @@ impl Parser { or_replace, name, args, - returns: return_type, + returns, append_only, params, }) } - pub fn parse_declare(&mut self) -> Result { + pub fn parse_declare(&mut self) -> PResult { Ok(Statement::DeclareCursor { stmt: DeclareCursorStatement::parse_to(self)?, }) } - pub fn parse_fetch_cursor(&mut self) -> Result { + pub fn parse_fetch_cursor(&mut self) -> PResult { Ok(Statement::FetchCursor { stmt: FetchCursorStatement::parse_to(self)?, }) } - pub fn parse_close_cursor(&mut self) -> Result { + pub fn parse_close_cursor(&mut self) -> PResult { Ok(Statement::CloseCursor { stmt: CloseCursorStatement::parse_to(self)?, }) } - fn parse_table_column_def(&mut self) -> Result { + fn parse_table_column_def(&mut self) -> PResult { Ok(TableColumnDef { name: self.parse_identifier_non_reserved()?, data_type: self.parse_data_type()?, }) } - fn parse_function_arg(&mut self) -> Result { + fn parse_function_arg(&mut self) -> PResult { let mode = if self.parse_keyword(Keyword::IN) { Some(ArgMode::In) } else if self.parse_keyword(Keyword::OUT) { @@ -2424,14 +2258,12 @@ impl Parser { }) } - fn parse_create_function_body(&mut self) -> Result { + fn parse_create_function_body(&mut self) -> PResult { let mut body = CreateFunctionBody::default(); loop { - fn ensure_not_set(field: &Option, name: &str) -> Result<(), ParserError> { + fn ensure_not_set(field: &Option, name: &str) -> PResult<()> { if field.is_some() { - return Err(ParserError::ParserError(format!( - "{name} specified more than once", - ))); + parser_err!("{name} specified more than once"); } Ok(()) } @@ -2443,7 +2275,7 @@ impl Parser { body.language = Some(self.parse_identifier()?); } else if self.parse_keyword(Keyword::RUNTIME) { ensure_not_set(&body.runtime, "RUNTIME")?; - body.runtime = Some(self.parse_function_runtime()?); + body.runtime = Some(self.parse_identifier()?); } else if self.parse_keyword(Keyword::IMMUTABLE) { ensure_not_set(&body.behavior, "IMMUTABLE | STABLE | VOLATILE")?; body.behavior = Some(FunctionBehavior::Immutable); @@ -2474,7 +2306,7 @@ impl Parser { } } - fn parse_create_function_using(&mut self) -> Result { + fn parse_create_function_using(&mut self) -> PResult { let keyword = self.expect_one_of_keywords(&[Keyword::LINK, Keyword::BASE64])?; match keyword { @@ -2490,22 +2322,11 @@ impl Parser { } } - fn parse_function_runtime(&mut self) -> Result { - let ident = self.parse_identifier()?; - match ident.value.to_lowercase().as_str() { - "deno" => Ok(FunctionRuntime::Deno), - "quickjs" => Ok(FunctionRuntime::QuickJs), - r => Err(ParserError::ParserError(format!( - "Unsupported runtime: {r}" - ))), - } - } - fn parse_function_type( &mut self, is_async: bool, is_generator: bool, - ) -> Result { + ) -> PResult { let is_generator = if is_generator { true } else { @@ -2527,25 +2348,32 @@ impl Parser { // | CREATEUSER | NOCREATEUSER // | LOGIN | NOLOGIN // | [ ENCRYPTED ] PASSWORD 'password' | PASSWORD NULL | OAUTH - fn parse_create_user(&mut self) -> Result { + fn parse_create_user(&mut self) -> PResult { Ok(Statement::CreateUser(CreateUserStatement::parse_to(self)?)) } - pub fn parse_with_properties(&mut self) -> Result, ParserError> { + fn parse_create_secret(&mut self) -> PResult { + Ok(Statement::CreateSecret { + stmt: CreateSecretStatement::parse_to(self)?, + }) + } + + pub fn parse_with_properties(&mut self) -> PResult> { Ok(self .parse_options_with_preceding_keyword(Keyword::WITH)? .to_vec()) } - pub fn parse_discard(&mut self) -> Result { - self.expect_keyword(Keyword::ALL) - .map_err(|_| ParserError::ParserError("only DISCARD ALL is supported".to_string()))?; + pub fn parse_discard(&mut self) -> PResult { + self.expect_keyword(Keyword::ALL)?; Ok(Statement::Discard(DiscardType::All)) } - pub fn parse_drop(&mut self) -> Result { + pub fn parse_drop(&mut self) -> PResult { if self.parse_keyword(Keyword::FUNCTION) { return self.parse_drop_function(); + } else if self.parse_keyword(Keyword::AGGREGATE) { + return self.parse_drop_aggregate(); } Ok(Statement::Drop(DropStatement::parse_to(self)?)) } @@ -2554,7 +2382,7 @@ impl Parser { /// DROP FUNCTION [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...] /// [ CASCADE | RESTRICT ] /// ``` - fn parse_drop_function(&mut self) -> Result { + fn parse_drop_function(&mut self) -> PResult { let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); let func_desc = self.parse_comma_separated(Parser::parse_function_desc)?; let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) { @@ -2569,7 +2397,26 @@ impl Parser { }) } - fn parse_function_desc(&mut self) -> Result { + /// ```sql + /// DROP AGGREGATE [ IF EXISTS ] name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] [, ...] + /// [ CASCADE | RESTRICT ] + /// ``` + fn parse_drop_aggregate(&mut self) -> PResult { + let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); + let func_desc = self.parse_comma_separated(Parser::parse_function_desc)?; + let option = match self.parse_one_of_keywords(&[Keyword::CASCADE, Keyword::RESTRICT]) { + Some(Keyword::CASCADE) => Some(ReferentialAction::Cascade), + Some(Keyword::RESTRICT) => Some(ReferentialAction::Restrict), + _ => None, + }; + Ok(Statement::DropAggregate { + if_exists, + func_desc, + option, + }) + } + + fn parse_function_desc(&mut self) -> PResult { let name = self.parse_object_name()?; let args = if self.consume_token(&Token::LParen) { @@ -2587,7 +2434,7 @@ impl Parser { Ok(FunctionDesc { name, args }) } - pub fn parse_create_index(&mut self, unique: bool) -> Result { + pub fn parse_create_index(&mut self, unique: bool) -> PResult { let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let index_name = self.parse_object_name()?; self.expect_keyword(Keyword::ON)?; @@ -2618,7 +2465,7 @@ impl Parser { }) } - pub fn parse_with_version_column(&mut self) -> Result, ParserError> { + pub fn parse_with_version_column(&mut self) -> PResult> { if self.parse_keywords(&[Keyword::WITH, Keyword::VERSION, Keyword::COLUMN]) { self.expect_token(&Token::LParen)?; let name = self.parse_identifier_non_reserved()?; @@ -2629,7 +2476,7 @@ impl Parser { } } - pub fn parse_on_conflict(&mut self) -> Result, ParserError> { + pub fn parse_on_conflict(&mut self) -> PResult> { if self.parse_keywords(&[Keyword::ON, Keyword::CONFLICT]) { self.parse_handle_conflict_behavior() } else { @@ -2637,11 +2484,7 @@ impl Parser { } } - pub fn parse_create_table( - &mut self, - or_replace: bool, - temporary: bool, - ) -> Result { + pub fn parse_create_table(&mut self, or_replace: bool, temporary: bool) -> PResult { let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); let table_name = self.parse_object_name()?; // parse optional column list (schema) and watermarks on source. @@ -2676,9 +2519,7 @@ impl Parser { // Parse optional `AS ( query )` let query = if self.parse_keyword(Keyword::AS) { if !source_watermarks.is_empty() { - return Err(ParserError::ParserError( - "Watermarks can't be defined on table created by CREATE TABLE AS".to_string(), - )); + parser_err!("Watermarks can't be defined on table created by CREATE TABLE AS"); } Some(Box::new(self.parse_query()?)) } else { @@ -2717,7 +2558,7 @@ impl Parser { }) } - pub fn parse_include_options(&mut self) -> Result { + pub fn parse_include_options(&mut self) -> PResult { let mut options = vec![]; while self.parse_keyword(Keyword::INCLUDE) { let column_type = self.parse_identifier()?; @@ -2761,7 +2602,7 @@ impl Parser { Ok(options) } - pub fn parse_columns_with_watermark(&mut self) -> Result { + pub fn parse_columns_with_watermark(&mut self) -> PResult { let mut columns = vec![]; let mut constraints = vec![]; let mut watermarks = vec![]; @@ -2775,9 +2616,7 @@ impl Parser { if wildcard_idx.is_none() { wildcard_idx = Some(columns.len()); } else { - return Err(ParserError::ParserError( - "At most 1 wildcard is allowed in source definetion".to_string(), - )); + parser_err!("At most 1 wildcard is allowed in source definetion"); } } else if let Some(constraint) = self.parse_optional_table_constraint()? { constraints.push(constraint); @@ -2785,28 +2624,26 @@ impl Parser { watermarks.push(watermark); if watermarks.len() > 1 { // TODO(yuhao): allow multiple watermark on source. - return Err(ParserError::ParserError( - "Only 1 watermark is allowed to be defined on source.".to_string(), - )); + parser_err!("Only 1 watermark is allowed to be defined on source."); } } else if let Token::Word(_) = self.peek_token().token { columns.push(self.parse_column_def()?); } else { - return self.expected("column name or constraint definition", self.peek_token()); + return self.expected("column name or constraint definition"); } let comma = self.consume_token(&Token::Comma); if self.consume_token(&Token::RParen) { // allow a trailing comma, even though it's not in standard break; } else if !comma { - return self.expected("',' or ')' after column definition", self.peek_token()); + return self.expected("',' or ')' after column definition"); } } Ok((columns, constraints, watermarks, wildcard_idx)) } - fn parse_column_def(&mut self) -> Result { + fn parse_column_def(&mut self) -> PResult { let name = self.parse_identifier_non_reserved()?; let data_type = if let Token::Word(_) = self.peek_token().token { Some(self.parse_data_type()?) @@ -2826,10 +2663,7 @@ impl Parser { if let Some(option) = self.parse_optional_column_option()? { options.push(ColumnOptionDef { name, option }); } else { - return self.expected( - "constraint details after CONSTRAINT ", - self.peek_token(), - ); + return self.expected("constraint details after CONSTRAINT "); } } else if let Some(option) = self.parse_optional_column_option()? { options.push(ColumnOptionDef { name: None, option }); @@ -2845,7 +2679,7 @@ impl Parser { }) } - pub fn parse_optional_column_option(&mut self) -> Result, ParserError> { + pub fn parse_optional_column_option(&mut self) -> PResult> { if self.parse_keywords(&[Keyword::NOT, Keyword::NULL]) { Ok(Some(ColumnOption::NotNull)) } else if self.parse_keyword(Keyword::NULL) { @@ -2892,7 +2726,7 @@ impl Parser { } } - pub fn parse_handle_conflict_behavior(&mut self) -> Result, ParserError> { + pub fn parse_handle_conflict_behavior(&mut self) -> PResult> { if self.parse_keyword(Keyword::OVERWRITE) { Ok(Some(OnConflict::OverWrite)) } else if self.parse_keyword(Keyword::IGNORE) { @@ -2910,7 +2744,7 @@ impl Parser { } } - pub fn parse_referential_action(&mut self) -> Result { + pub fn parse_referential_action(&mut self) -> PResult { if self.parse_keyword(Keyword::RESTRICT) { Ok(ReferentialAction::Restrict) } else if self.parse_keyword(Keyword::CASCADE) { @@ -2922,14 +2756,11 @@ impl Parser { } else if self.parse_keywords(&[Keyword::SET, Keyword::DEFAULT]) { Ok(ReferentialAction::SetDefault) } else { - self.expected( - "one of RESTRICT, CASCADE, SET NULL, NO ACTION or SET DEFAULT", - self.peek_token(), - ) + self.expected("one of RESTRICT, CASCADE, SET NULL, NO ACTION or SET DEFAULT") } } - pub fn parse_optional_watermark(&mut self) -> Result, ParserError> { + pub fn parse_optional_watermark(&mut self) -> PResult> { if self.parse_keyword(Keyword::WATERMARK) { self.expect_keyword(Keyword::FOR)?; let column = self.parse_identifier_non_reserved()?; @@ -2941,14 +2772,13 @@ impl Parser { } } - pub fn parse_optional_table_constraint( - &mut self, - ) -> Result, ParserError> { + pub fn parse_optional_table_constraint(&mut self) -> PResult> { let name = if self.parse_keyword(Keyword::CONSTRAINT) { Some(self.parse_identifier_non_reserved()?) } else { None }; + let checkpoint = *self; let token = self.next_token(); match token.token { Token::Word(w) if w.keyword == Keyword::PRIMARY || w.keyword == Keyword::UNIQUE => { @@ -2997,14 +2827,11 @@ impl Parser { self.expect_token(&Token::RParen)?; Ok(Some(TableConstraint::Check { name, expr })) } - unexpected => { + _ => { + *self = checkpoint; if name.is_some() { - self.expected( - "PRIMARY, UNIQUE, FOREIGN, or CHECK", - unexpected.with_location(token.location), - ) + self.expected("PRIMARY, UNIQUE, FOREIGN, or CHECK") } else { - self.prev_token(); Ok(None) } } @@ -3014,7 +2841,7 @@ impl Parser { pub fn parse_options_with_preceding_keyword( &mut self, keyword: Keyword, - ) -> Result, ParserError> { + ) -> PResult> { if self.parse_keyword(keyword) { self.expect_token(&Token::LParen)?; self.parse_options_inner() @@ -3023,7 +2850,7 @@ impl Parser { } } - pub fn parse_options(&mut self) -> Result, ParserError> { + pub fn parse_options(&mut self) -> PResult> { if self.peek_token() == Token::LParen { self.next_token(); self.parse_options_inner() @@ -3033,7 +2860,7 @@ impl Parser { } // has parsed a LParen - pub fn parse_options_inner(&mut self) -> Result, ParserError> { + pub fn parse_options_inner(&mut self) -> PResult> { let mut values = vec![]; loop { values.push(Parser::parse_sql_option(self)?); @@ -3042,21 +2869,22 @@ impl Parser { // allow a trailing comma, even though it's not in standard break; } else if !comma { - return self.expected("',' or ')' after option definition", self.peek_token()); + return self.expected("',' or ')' after option definition"); } } Ok(values) } - pub fn parse_sql_option(&mut self) -> Result { + pub fn parse_sql_option(&mut self) -> PResult { let name = self.parse_object_name()?; self.expect_token(&Token::Eq)?; let value = self.parse_value()?; Ok(SqlOption { name, value }) } - pub fn parse_since(&mut self) -> Result, ParserError> { + pub fn parse_since(&mut self) -> PResult> { if self.parse_keyword(Keyword::SINCE) { + let checkpoint = *self; let token = self.next_token(); match token.token { Token::Word(w) => { @@ -3071,29 +2899,26 @@ impl Parser { self.expect_token(&Token::RParen)?; Ok(Some(Since::Begin)) } else { - parser_err!(format!( + parser_err!( "Expected proctime(), begin() or now(), found: {}", ident.real_value() - )) + ) } } Token::Number(s) => { - let num = s.parse::().map_err(|e| { - ParserError::ParserError(format!("Could not parse '{}' as u64: {}", s, e)) - }); - Ok(Some(Since::TimestampMsNum(num?))) + let num = s + .parse::() + .map_err(|e| StrError(format!("Could not parse '{}' as u64: {}", s, e)))?; + Ok(Some(Since::TimestampMsNum(num))) } - unexpected => self.expected( - "proctime(), begin() , now(), Number", - unexpected.with_location(token.location), - ), + _ => self.expected_at(checkpoint, "proctime(), begin() , now(), Number"), } } else { Ok(None) } } - pub fn parse_emit_mode(&mut self) -> Result, ParserError> { + pub fn parse_emit_mode(&mut self) -> PResult> { if self.parse_keyword(Keyword::EMIT) { match self.parse_one_of_keywords(&[Keyword::IMMEDIATELY, Keyword::ON]) { Some(Keyword::IMMEDIATELY) => Ok(Some(EmitMode::Immediately)), @@ -3102,17 +2927,14 @@ impl Parser { Ok(Some(EmitMode::OnWindowClose)) } Some(_) => unreachable!(), - None => self.expected( - "IMMEDIATELY or ON WINDOW CLOSE after EMIT", - self.peek_token(), - ), + None => self.expected("IMMEDIATELY or ON WINDOW CLOSE after EMIT"), } } else { Ok(None) } } - pub fn parse_alter(&mut self) -> Result { + pub fn parse_alter(&mut self) -> PResult { if self.parse_keyword(Keyword::DATABASE) { self.parse_alter_database() } else if self.parse_keyword(Keyword::SCHEMA) { @@ -3141,13 +2963,12 @@ impl Parser { self.parse_alter_subscription() } else { self.expected( - "DATABASE, SCHEMA, TABLE, INDEX, MATERIALIZED, VIEW, SINK, SUBSCRIPTION, SOURCE, FUNCTION, USER or SYSTEM after ALTER", - self.peek_token(), + "DATABASE, SCHEMA, TABLE, INDEX, MATERIALIZED, VIEW, SINK, SUBSCRIPTION, SOURCE, FUNCTION, USER or SYSTEM after ALTER" ) } } - pub fn parse_alter_database(&mut self) -> Result { + pub fn parse_alter_database(&mut self) -> PResult { let database_name = self.parse_object_name()?; let operation = if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { let owner_name: Ident = self.parse_identifier()?; @@ -3159,10 +2980,10 @@ impl Parser { let database_name = self.parse_object_name()?; AlterDatabaseOperation::RenameDatabase { database_name } } else { - return self.expected("TO after RENAME", self.peek_token()); + return self.expected("TO after RENAME"); } } else { - return self.expected("OWNER TO after ALTER DATABASE", self.peek_token()); + return self.expected("OWNER TO after ALTER DATABASE"); }; Ok(Statement::AlterDatabase { @@ -3171,7 +2992,7 @@ impl Parser { }) } - pub fn parse_alter_schema(&mut self) -> Result { + pub fn parse_alter_schema(&mut self) -> PResult { let schema_name = self.parse_object_name()?; let operation = if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { let owner_name: Ident = self.parse_identifier()?; @@ -3179,14 +3000,11 @@ impl Parser { new_owner_name: owner_name, } } else if self.parse_keyword(Keyword::RENAME) { - if self.parse_keyword(Keyword::TO) { - let schema_name = self.parse_object_name()?; - AlterSchemaOperation::RenameSchema { schema_name } - } else { - return self.expected("TO after RENAME", self.peek_token()); - } + self.expect_keyword(Keyword::TO)?; + let schema_name = self.parse_object_name()?; + AlterSchemaOperation::RenameSchema { schema_name } } else { - return self.expected("RENAME OR OWNER TO after ALTER SCHEMA", self.peek_token()); + return self.expected("RENAME OR OWNER TO after ALTER SCHEMA"); }; Ok(Statement::AlterSchema { @@ -3195,11 +3013,11 @@ impl Parser { }) } - pub fn parse_alter_user(&mut self) -> Result { + pub fn parse_alter_user(&mut self) -> PResult { Ok(Statement::AlterUser(AlterUserStatement::parse_to(self)?)) } - pub fn parse_alter_table(&mut self) -> Result { + pub fn parse_alter_table(&mut self) -> PResult { let _ = self.parse_keyword(Keyword::ONLY); let table_name = self.parse_object_name()?; let operation = if self.parse_keyword(Keyword::ADD) { @@ -3246,10 +3064,7 @@ impl Parser { if self.expect_keyword(Keyword::TO).is_err() && self.expect_token(&Token::Eq).is_err() { - return self.expected( - "TO or = after ALTER TABLE SET PARALLELISM", - self.peek_token(), - ); + return self.expected("TO or = after ALTER TABLE SET PARALLELISM"); } let value = self.parse_set_variable()?; @@ -3260,8 +3075,10 @@ impl Parser { parallelism: value, deferred, } + } else if let Some(rate_limit) = self.parse_alter_streaming_rate_limit()? { + AlterTableOperation::SetStreamingRateLimit { rate_limit } } else { - return self.expected("SCHEMA/PARALLELISM after SET", self.peek_token()); + return self.expected("SCHEMA/PARALLELISM/STREAMING_RATE_LIMIT after SET"); } } else if self.parse_keyword(Keyword::DROP) { let _ = self.parse_keyword(Keyword::COLUMN); @@ -3298,19 +3115,14 @@ impl Parser { }; AlterColumnOperation::SetDataType { data_type, using } } else { - return self.expected( - "SET/DROP NOT NULL, SET DEFAULT, SET DATA TYPE after ALTER COLUMN", - self.peek_token(), - ); + return self + .expected("SET/DROP NOT NULL, SET DEFAULT, SET DATA TYPE after ALTER COLUMN"); }; AlterTableOperation::AlterColumn { column_name, op } } else if self.parse_keywords(&[Keyword::REFRESH, Keyword::SCHEMA]) { AlterTableOperation::RefreshSchema } else { - return self.expected( - "ADD or RENAME or OWNER TO or SET or DROP after ALTER TABLE", - self.peek_token(), - ); + return self.expected("ADD or RENAME or OWNER TO or SET or DROP after ALTER TABLE"); }; Ok(Statement::AlterTable { name: table_name, @@ -3318,24 +3130,43 @@ impl Parser { }) } - pub fn parse_alter_index(&mut self) -> Result { + /// STREAMING_RATE_LIMIT = default | NUMBER + /// STREAMING_RATE_LIMIT TO default | NUMBER + pub fn parse_alter_streaming_rate_limit(&mut self) -> PResult> { + if !self.parse_keyword(Keyword::STREAMING_RATE_LIMIT) { + return Ok(None); + } + if self.expect_keyword(Keyword::TO).is_err() && self.expect_token(&Token::Eq).is_err() { + return self.expected("TO or = after ALTER TABLE SET STREAMING_RATE_LIMIT"); + } + let rate_limit = if self.parse_keyword(Keyword::DEFAULT) { + -1 + } else { + let s = self.parse_number_value()?; + if let Ok(n) = s.parse::() { + n + } else { + return self.expected("number or DEFAULT"); + } + }; + Ok(Some(rate_limit)) + } + + pub fn parse_alter_index(&mut self) -> PResult { let index_name = self.parse_object_name()?; let operation = if self.parse_keyword(Keyword::RENAME) { if self.parse_keyword(Keyword::TO) { let index_name = self.parse_object_name()?; AlterIndexOperation::RenameIndex { index_name } } else { - return self.expected("TO after RENAME", self.peek_token()); + return self.expected("TO after RENAME"); } } else if self.parse_keyword(Keyword::SET) { if self.parse_keyword(Keyword::PARALLELISM) { if self.expect_keyword(Keyword::TO).is_err() && self.expect_token(&Token::Eq).is_err() { - return self.expected( - "TO or = after ALTER TABLE SET PARALLELISM", - self.peek_token(), - ); + return self.expected("TO or = after ALTER TABLE SET PARALLELISM"); } let value = self.parse_set_variable()?; @@ -3347,10 +3178,10 @@ impl Parser { deferred, } } else { - return self.expected("PARALLELISM after SET", self.peek_token()); + return self.expected("PARALLELISM after SET"); } } else { - return self.expected("RENAME after ALTER INDEX", self.peek_token()); + return self.expected("RENAME after ALTER INDEX"); }; Ok(Statement::AlterIndex { @@ -3359,14 +3190,14 @@ impl Parser { }) } - pub fn parse_alter_view(&mut self, materialized: bool) -> Result { + pub fn parse_alter_view(&mut self, materialized: bool) -> PResult { let view_name = self.parse_object_name()?; let operation = if self.parse_keyword(Keyword::RENAME) { if self.parse_keyword(Keyword::TO) { let view_name = self.parse_object_name()?; AlterViewOperation::RenameView { view_name } } else { - return self.expected("TO after RENAME", self.peek_token()); + return self.expected("TO after RENAME"); } } else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { let owner_name: Ident = self.parse_identifier()?; @@ -3383,10 +3214,7 @@ impl Parser { if self.expect_keyword(Keyword::TO).is_err() && self.expect_token(&Token::Eq).is_err() { - return self.expected( - "TO or = after ALTER TABLE SET PARALLELISM", - self.peek_token(), - ); + return self.expected("TO or = after ALTER TABLE SET PARALLELISM"); } let value = self.parse_set_variable()?; @@ -3397,17 +3225,18 @@ impl Parser { parallelism: value, deferred, } + } else if materialized + && let Some(rate_limit) = self.parse_alter_streaming_rate_limit()? + { + AlterViewOperation::SetStreamingRateLimit { rate_limit } } else { - return self.expected("SCHEMA/PARALLELISM after SET", self.peek_token()); + return self.expected("SCHEMA/PARALLELISM/STREAMING_RATE_LIMIT after SET"); } } else { - return self.expected( - &format!( - "RENAME or OWNER TO or SET after ALTER {}VIEW", - if materialized { "MATERIALIZED " } else { "" } - ), - self.peek_token(), - ); + return self.expected(&format!( + "RENAME or OWNER TO or SET after ALTER {}VIEW", + if materialized { "MATERIALIZED " } else { "" } + )); }; Ok(Statement::AlterView { @@ -3417,14 +3246,14 @@ impl Parser { }) } - pub fn parse_alter_sink(&mut self) -> Result { + pub fn parse_alter_sink(&mut self) -> PResult { let sink_name = self.parse_object_name()?; let operation = if self.parse_keyword(Keyword::RENAME) { if self.parse_keyword(Keyword::TO) { let sink_name = self.parse_object_name()?; AlterSinkOperation::RenameSink { sink_name } } else { - return self.expected("TO after RENAME", self.peek_token()); + return self.expected("TO after RENAME"); } } else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { let owner_name: Ident = self.parse_identifier()?; @@ -3441,10 +3270,7 @@ impl Parser { if self.expect_keyword(Keyword::TO).is_err() && self.expect_token(&Token::Eq).is_err() { - return self.expected( - "TO or = after ALTER TABLE SET PARALLELISM", - self.peek_token(), - ); + return self.expected("TO or = after ALTER TABLE SET PARALLELISM"); } let value = self.parse_set_variable()?; @@ -3455,13 +3281,10 @@ impl Parser { deferred, } } else { - return self.expected("SCHEMA/PARALLELISM after SET", self.peek_token()); + return self.expected("SCHEMA/PARALLELISM after SET"); } } else { - return self.expected( - "RENAME or OWNER TO or SET after ALTER SINK", - self.peek_token(), - ); + return self.expected("RENAME or OWNER TO or SET after ALTER SINK"); }; Ok(Statement::AlterSink { @@ -3470,14 +3293,14 @@ impl Parser { }) } - pub fn parse_alter_subscription(&mut self) -> Result { + pub fn parse_alter_subscription(&mut self) -> PResult { let subscription_name = self.parse_object_name()?; let operation = if self.parse_keyword(Keyword::RENAME) { if self.parse_keyword(Keyword::TO) { let subscription_name = self.parse_object_name()?; AlterSubscriptionOperation::RenameSubscription { subscription_name } } else { - return self.expected("TO after RENAME", self.peek_token()); + return self.expected("TO after RENAME"); } } else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) { let owner_name: Ident = self.parse_identifier()?; @@ -3490,30 +3313,11 @@ impl Parser { AlterSubscriptionOperation::SetSchema { new_schema_name: schema_name, } - } else if self.parse_keyword(Keyword::PARALLELISM) { - if self.expect_keyword(Keyword::TO).is_err() - && self.expect_token(&Token::Eq).is_err() - { - return self.expected( - "TO or = after ALTER TABLE SET PARALLELISM", - self.peek_token(), - ); - } - let value = self.parse_set_variable()?; - let deferred = self.parse_keyword(Keyword::DEFERRED); - - AlterSubscriptionOperation::SetParallelism { - parallelism: value, - deferred, - } } else { - return self.expected("SCHEMA/PARALLELISM after SET", self.peek_token()); + return self.expected("SCHEMA after SET"); } } else { - return self.expected( - "RENAME or OWNER TO or SET after ALTER SUBSCRIPTION", - self.peek_token(), - ); + return self.expected("RENAME or OWNER TO or SET after ALTER SUBSCRIPTION"); }; Ok(Statement::AlterSubscription { @@ -3522,14 +3326,14 @@ impl Parser { }) } - pub fn parse_alter_source(&mut self) -> Result { + pub fn parse_alter_source(&mut self) -> PResult { let source_name = self.parse_object_name()?; let operation = if self.parse_keyword(Keyword::RENAME) { if self.parse_keyword(Keyword::TO) { let source_name = self.parse_object_name()?; AlterSourceOperation::RenameSource { source_name } } else { - return self.expected("TO after RENAME", self.peek_token()); + return self.expected("TO after RENAME"); } } else if self.parse_keyword(Keyword::ADD) { let _ = self.parse_keyword(Keyword::COLUMN); @@ -3547,18 +3351,22 @@ impl Parser { AlterSourceOperation::SetSchema { new_schema_name: schema_name, } + } else if let Some(rate_limit) = self.parse_alter_streaming_rate_limit()? { + AlterSourceOperation::SetStreamingRateLimit { rate_limit } } else { - return self.expected("SCHEMA after SET", self.peek_token()); + return self.expected("SCHEMA after SET"); } } else if self.peek_nth_any_of_keywords(0, &[Keyword::FORMAT]) { let connector_schema = self.parse_schema()?.unwrap(); + if connector_schema.key_encode.is_some() { + parser_err!("key encode clause is not supported in source schema"); + } AlterSourceOperation::FormatEncode { connector_schema } } else if self.parse_keywords(&[Keyword::REFRESH, Keyword::SCHEMA]) { AlterSourceOperation::RefreshSchema } else { return self.expected( - "RENAME, ADD COLUMN or OWNER TO or SET after ALTER SOURCE", - self.peek_token(), + "RENAME, ADD COLUMN, OWNER TO, SET or STREAMING_RATE_LIMIT after ALTER SOURCE", ); }; @@ -3568,7 +3376,7 @@ impl Parser { }) } - pub fn parse_alter_function(&mut self) -> Result { + pub fn parse_alter_function(&mut self) -> PResult { let FunctionDesc { name, args } = self.parse_function_desc()?; let operation = if self.parse_keyword(Keyword::SET) { @@ -3578,10 +3386,10 @@ impl Parser { new_schema_name: schema_name, } } else { - return self.expected("SCHEMA after SET", self.peek_token()); + return self.expected("SCHEMA after SET"); } } else { - return self.expected("SET after ALTER FUNCTION", self.peek_token()); + return self.expected("SET after ALTER FUNCTION"); }; Ok(Statement::AlterFunction { @@ -3591,7 +3399,7 @@ impl Parser { }) } - pub fn parse_alter_connection(&mut self) -> Result { + pub fn parse_alter_connection(&mut self) -> PResult { let connection_name = self.parse_object_name()?; let operation = if self.parse_keyword(Keyword::SET) { if self.parse_keyword(Keyword::SCHEMA) { @@ -3600,10 +3408,10 @@ impl Parser { new_schema_name: schema_name, } } else { - return self.expected("SCHEMA after SET", self.peek_token()); + return self.expected("SCHEMA after SET"); } } else { - return self.expected("SET after ALTER CONNECTION", self.peek_token()); + return self.expected("SET after ALTER CONNECTION"); }; Ok(Statement::AlterConnection { @@ -3612,18 +3420,18 @@ impl Parser { }) } - pub fn parse_alter_system(&mut self) -> Result { + pub fn parse_alter_system(&mut self) -> PResult { self.expect_keyword(Keyword::SET)?; let param = self.parse_identifier()?; if self.expect_keyword(Keyword::TO).is_err() && self.expect_token(&Token::Eq).is_err() { - return self.expected("TO or = after ALTER SYSTEM SET", self.peek_token()); + return self.expected("TO or = after ALTER SYSTEM SET"); } let value = self.parse_set_variable()?; Ok(Statement::AlterSystem { param, value }) } /// Parse a copy statement - pub fn parse_copy(&mut self) -> Result { + pub fn parse_copy(&mut self) -> PResult { let table_name = self.parse_object_name()?; let columns = self.parse_parenthesized_column_list(Optional)?; self.expect_keywords(&[Keyword::FROM, Keyword::STDIN])?; @@ -3674,7 +3482,8 @@ impl Parser { } /// Parse a literal value (numbers, strings, date/time, booleans) - fn parse_value(&mut self) -> Result { + pub fn parse_value(&mut self) -> PResult { + let checkpoint = *self; let token = self.next_token(); match token.token { Token::Word(w) => match w.keyword { @@ -3684,12 +3493,9 @@ impl Parser { Keyword::NoKeyword if w.quote_style.is_some() => match w.quote_style { Some('"') => Ok(Value::DoubleQuotedString(w.value)), Some('\'') => Ok(Value::SingleQuotedString(w.value)), - _ => self.expected("A value?", Token::Word(w).with_location(token.location))?, + _ => self.expected_at(checkpoint, "A value")?, }, - _ => self.expected( - "a concrete value", - Token::Word(w).with_location(token.location), - ), + _ => self.expected_at(checkpoint, "a concrete value"), }, Token::Number(ref n) => Ok(Value::Number(n.clone())), Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())), @@ -3697,95 +3503,77 @@ impl Parser { Token::CstyleEscapesString(ref s) => Ok(Value::CstyleEscapedString(s.clone())), Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())), Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())), - unexpected => self.expected("a value", unexpected.with_location(token.location)), - } - } - - fn parse_set_variable(&mut self) -> Result { - let mut values = vec![]; - loop { - let token = self.peek_token(); - let value = match (self.parse_value(), token.token) { - (Ok(value), _) => SetVariableValueSingle::Literal(value), - (Err(_), Token::Word(w)) => { - if w.keyword == Keyword::DEFAULT { - if !values.is_empty() { - self.expected( - "parameter list value", - Token::Word(w).with_location(token.location), - )? + _ => self.expected_at(checkpoint, "a value"), + } + } + + fn parse_set_variable(&mut self) -> PResult { + alt(( + Keyword::DEFAULT.value(SetVariableValue::Default), + separated( + 1.., + alt(( + Self::parse_value.map(SetVariableValueSingle::Literal), + |parser: &mut Self| { + let checkpoint = *parser; + let ident = parser.parse_identifier()?; + if ident.value == "default" { + *parser = checkpoint; + return parser.expected("parameter list value").map_err(|e| e.cut()); } - return Ok(SetVariableValue::Default); - } else { - SetVariableValueSingle::Ident(w.to_ident()?) - } - } - (Err(_), unexpected) => { - self.expected("parameter value", unexpected.with_location(token.location))? + Ok(SetVariableValueSingle::Ident(ident)) + }, + fail.expect("parameter value"), + )), + Token::Comma, + ) + .map(|list: Vec| { + if list.len() == 1 { + SetVariableValue::Single(list[0].clone()) + } else { + SetVariableValue::List(list) } - }; - values.push(value); - if !self.consume_token(&Token::Comma) { - break; - } - } - if values.len() == 1 { - Ok(SetVariableValue::Single(values[0].clone())) - } else { - Ok(SetVariableValue::List(values)) - } + }), + )) + .parse_next(self) } - pub fn parse_number_value(&mut self) -> Result { + pub fn parse_number_value(&mut self) -> PResult { + let checkpoint = *self; match self.parse_value()? { Value::Number(v) => Ok(v), - _ => { - self.prev_token(); - self.expected("literal number", self.peek_token()) - } + _ => self.expected_at(checkpoint, "literal number"), } } /// Parse an unsigned literal integer/long - pub fn parse_literal_uint(&mut self) -> Result { - let token = self.next_token(); - match token.token { - Token::Number(s) => s.parse::().map_err(|e| { - ParserError::ParserError(format!("Could not parse '{}' as u64: {}", s, e)) - }), - unexpected => self.expected("literal int", unexpected.with_location(token.location)), - } + pub fn parse_literal_uint(&mut self) -> PResult { + literal_uint(self) } - pub fn parse_function_definition(&mut self) -> Result { - let peek_token = self.peek_token(); - match peek_token.token { - Token::DollarQuotedString(value) => { - self.next_token(); - Ok(FunctionDefinition::DoubleDollarDef(value.value)) - } - _ => Ok(FunctionDefinition::SingleQuotedDef( - self.parse_literal_string()?, - )), - } + pub fn parse_function_definition(&mut self) -> PResult { + alt(( + single_quoted_string.map(FunctionDefinition::SingleQuotedDef), + dollar_quoted_string.map(FunctionDefinition::DoubleDollarDef), + Self::parse_identifier.map(|i| FunctionDefinition::Identifier(i.value)), + fail.expect("function definition"), + )) + .parse_next(self) } /// Parse a literal string - pub fn parse_literal_string(&mut self) -> Result { + pub fn parse_literal_string(&mut self) -> PResult { + let checkpoint = *self; let token = self.next_token(); match token.token { - Token::Word(Word { - value, - keyword: Keyword::NoKeyword, - .. - }) => Ok(value), Token::SingleQuotedString(s) => Ok(s), - unexpected => self.expected("literal string", unexpected.with_location(token.location)), + _ => self.expected_at(checkpoint, "literal string"), } } /// Parse a map key string - pub fn parse_map_key(&mut self) -> Result { + pub fn parse_map_key(&mut self) -> PResult { + let checkpoint = *self; let token = self.next_token(); match token.token { Token::Word(Word { @@ -3800,156 +3588,22 @@ impl Parser { } Token::SingleQuotedString(s) => Ok(Expr::Value(Value::SingleQuotedString(s))), Token::Number(s) => Ok(Expr::Value(Value::Number(s))), - unexpected => self.expected( - "literal string, number or function", - unexpected.with_location(token.location), - ), + _ => self.expected_at(checkpoint, "literal string, number or function"), } } /// Parse a SQL datatype (in the context of a CREATE TABLE statement for example) and convert /// into an array of that datatype if needed - pub fn parse_data_type(&mut self) -> Result { - let mut data_type = self.parse_data_type_inner()?; - while self.consume_token(&Token::LBracket) { - self.expect_token(&Token::RBracket)?; - data_type = DataType::Array(Box::new(data_type)); - } - Ok(data_type) - } - - /// Parse struct `data_type` e.g.`>`. - pub fn parse_struct_data_type(&mut self) -> Result, ParserError> { - let mut columns = vec![]; - if !self.consume_token(&Token::Lt) { - return self.expected("'<' after struct", self.peek_token()); - } - self.angle_brackets_num += 1; - - loop { - if let Token::Word(_) = self.peek_token().token { - let name = self.parse_identifier_non_reserved()?; - let data_type = self.parse_data_type()?; - columns.push(StructField { name, data_type }) - } else { - return self.expected("struct field name", self.peek_token()); - } - if self.angle_brackets_num == 0 { - break; - } else if self.consume_token(&Token::Gt) { - self.angle_brackets_num -= 1; - break; - } else if self.consume_token(&Token::ShiftRight) { - if self.angle_brackets_num >= 1 { - self.angle_brackets_num -= 2; - break; - } else { - return parser_err!("too much '>'"); - } - } else if !self.consume_token(&Token::Comma) { - return self.expected("',' or '>' after column definition", self.peek_token()); - } - } - - Ok(columns) - } - - /// Parse a SQL datatype - pub fn parse_data_type_inner(&mut self) -> Result { - let token = self.next_token(); - match token.token { - Token::Word(w) => match w.keyword { - Keyword::BOOLEAN | Keyword::BOOL => Ok(DataType::Boolean), - Keyword::FLOAT => { - let precision = self.parse_optional_precision()?; - match precision { - Some(0) => Err(ParserError::ParserError( - "precision for type float must be at least 1 bit".to_string(), - )), - Some(54..) => Err(ParserError::ParserError( - "precision for type float must be less than 54 bits".to_string(), - )), - _ => Ok(DataType::Float(precision)), - } - } - Keyword::REAL => Ok(DataType::Real), - Keyword::DOUBLE => { - let _ = self.parse_keyword(Keyword::PRECISION); - Ok(DataType::Double) - } - Keyword::SMALLINT => Ok(DataType::SmallInt), - Keyword::INT | Keyword::INTEGER => Ok(DataType::Int), - Keyword::BIGINT => Ok(DataType::BigInt), - Keyword::STRING | Keyword::VARCHAR => Ok(DataType::Varchar), - Keyword::CHAR | Keyword::CHARACTER => { - if self.parse_keyword(Keyword::VARYING) { - Ok(DataType::Varchar) - } else { - Ok(DataType::Char(self.parse_optional_precision()?)) - } - } - Keyword::UUID => Ok(DataType::Uuid), - Keyword::DATE => Ok(DataType::Date), - Keyword::TIMESTAMP => { - let with_time_zone = self.parse_keyword(Keyword::WITH); - if with_time_zone || self.parse_keyword(Keyword::WITHOUT) { - self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?; - } - Ok(DataType::Timestamp(with_time_zone)) - } - Keyword::TIME => { - let with_time_zone = self.parse_keyword(Keyword::WITH); - if with_time_zone || self.parse_keyword(Keyword::WITHOUT) { - self.expect_keywords(&[Keyword::TIME, Keyword::ZONE])?; - } - Ok(DataType::Time(with_time_zone)) - } - // Interval types can be followed by a complicated interval - // qualifier that we don't currently support. See - // parse_interval_literal for a taste. - Keyword::INTERVAL => Ok(DataType::Interval), - Keyword::REGCLASS => Ok(DataType::Regclass), - Keyword::REGPROC => Ok(DataType::Regproc), - Keyword::TEXT => { - if self.consume_token(&Token::LBracket) { - // Note: this is postgresql-specific - self.expect_token(&Token::RBracket)?; - Ok(DataType::Array(Box::new(DataType::Text))) - } else { - Ok(DataType::Text) - } - } - Keyword::STRUCT => Ok(DataType::Struct(self.parse_struct_data_type()?)), - Keyword::BYTEA => Ok(DataType::Bytea), - Keyword::NUMERIC | Keyword::DECIMAL | Keyword::DEC => { - let (precision, scale) = self.parse_optional_precision_scale()?; - Ok(DataType::Decimal(precision, scale)) - } - _ => { - self.prev_token(); - let type_name = self.parse_object_name()?; - // JSONB is not a keyword - if type_name.to_string().eq_ignore_ascii_case("jsonb") { - Ok(DataType::Jsonb) - } else { - Ok(DataType::Custom(type_name)) - } - } - }, - unexpected => { - self.expected("a data type name", unexpected.with_location(token.location)) - } - } + pub fn parse_data_type(&mut self) -> PResult { + parser_v2::data_type(self) } /// Parse `AS identifier` (or simply `identifier` if it's not a reserved keyword) /// Some examples with aliases: `SELECT 1 foo`, `SELECT COUNT(*) AS cnt`, /// `SELECT ... FROM t1 foo, t2 bar`, `SELECT ... FROM (...) AS bar` - pub fn parse_optional_alias( - &mut self, - reserved_kwds: &[Keyword], - ) -> Result, ParserError> { + pub fn parse_optional_alias(&mut self, reserved_kwds: &[Keyword]) -> PResult> { let after_as = self.parse_keyword(Keyword::AS); + let checkpoint = *self; let token = self.next_token(); match token.token { // Accept any identifier after `AS` (though many dialects have restrictions on @@ -3960,14 +3614,11 @@ impl Parser { Token::Word(w) if after_as || (!reserved_kwds.contains(&w.keyword)) => { Ok(Some(w.to_ident()?)) } - not_an_ident => { + _ => { + *self = checkpoint; if after_as { - return self.expected( - "an identifier after AS", - not_an_ident.with_location(token.location), - ); + return self.expected("an identifier after AS"); } - self.prev_token(); Ok(None) // no alias found } } @@ -3980,7 +3631,7 @@ impl Parser { pub fn parse_optional_table_alias( &mut self, reserved_kwds: &[Keyword], - ) -> Result, ParserError> { + ) -> PResult> { match self.parse_optional_alias(reserved_kwds)? { Some(name) => { let columns = self.parse_parenthesized_column_list(Optional)?; @@ -3991,67 +3642,44 @@ impl Parser { } /// syntax `FOR SYSTEM_TIME AS OF PROCTIME()` is used for temporal join. - pub fn parse_as_of(&mut self) -> Result, ParserError> { - let after_for = self.parse_keyword(Keyword::FOR); - if after_for { - if self.peek_nth_any_of_keywords(0, &[Keyword::SYSTEM_TIME]) { - self.expect_keywords(&[Keyword::SYSTEM_TIME, Keyword::AS, Keyword::OF])?; - let token = self.next_token(); - match token.token { - Token::Word(w) => { - let ident = w.to_ident()?; - // Backward compatibility for now. - if ident.real_value() == "proctime" || ident.real_value() == "now" { - self.expect_token(&Token::LParen)?; - self.expect_token(&Token::RParen)?; - Ok(Some(AsOf::ProcessTime)) - } else { - parser_err!(format!("Expected proctime, found: {}", ident.real_value())) - } - } - Token::Number(s) => { - let num = s.parse::().map_err(|e| { - ParserError::ParserError(format!( - "Could not parse '{}' as i64: {}", - s, e - )) - }); - Ok(Some(AsOf::TimestampNum(num?))) - } - Token::SingleQuotedString(s) => Ok(Some(AsOf::TimestampString(s))), - unexpected => self.expected( - "Proctime(), Number or SingleQuotedString", - unexpected.with_location(token.location), - ), - } - } else { - self.expect_keywords(&[Keyword::SYSTEM_VERSION, Keyword::AS, Keyword::OF])?; - let token = self.next_token(); - match token.token { - Token::Number(s) => { - let num = s.parse::().map_err(|e| { - ParserError::ParserError(format!( - "Could not parse '{}' as i64: {}", - s, e - )) - }); - Ok(Some(AsOf::VersionNum(num?))) - } - Token::SingleQuotedString(s) => Ok(Some(AsOf::VersionString(s))), - unexpected => self.expected( - "Number or SingleQuotedString", - unexpected.with_location(token.location), - ), - } - } - } else { - Ok(None) - } + pub fn parse_as_of(&mut self) -> PResult { + Keyword::FOR.parse_next(self)?; + alt(( + preceded( + (Keyword::SYSTEM_TIME, Keyword::AS, Keyword::OF), + cut_err( + alt(( + ( + Self::parse_identifier.verify(|ident| { + ident.real_value() == "proctime" || ident.real_value() == "now" + }), + cut_err(Token::LParen), + cut_err(Token::RParen), + ) + .value(AsOf::ProcessTime), + literal_i64.map(AsOf::TimestampNum), + single_quoted_string.map(AsOf::TimestampString), + )) + .expect("proctime(), now(), number or string"), + ), + ), + preceded( + (Keyword::SYSTEM_VERSION, Keyword::AS, Keyword::OF), + cut_err( + alt(( + literal_i64.map(AsOf::VersionNum), + single_quoted_string.map(AsOf::VersionString), + )) + .expect("number or string"), + ), + ), + )) + .parse_next(self) } /// Parse a possibly qualified, possibly quoted identifier, e.g. /// `foo` or `myschema."table" - pub fn parse_object_name(&mut self) -> Result { + pub fn parse_object_name(&mut self) -> PResult { let mut idents = vec![]; loop { idents.push(self.parse_identifier()?); @@ -4063,7 +3691,7 @@ impl Parser { } /// Parse identifiers strictly i.e. don't parse keywords - pub fn parse_identifiers_non_keywords(&mut self) -> Result, ParserError> { + pub fn parse_identifiers_non_keywords(&mut self) -> PResult> { let mut idents = vec![]; loop { match self.peek_token().token { @@ -4085,7 +3713,7 @@ impl Parser { } /// Parse identifiers - pub fn parse_identifiers(&mut self) -> Result, ParserError> { + pub fn parse_identifiers(&mut self) -> PResult> { let mut idents = vec![]; loop { let token = self.next_token(); @@ -4102,33 +3730,32 @@ impl Parser { } /// Parse a simple one-word identifier (possibly quoted, possibly a keyword) - pub fn parse_identifier(&mut self) -> Result { + pub fn parse_identifier(&mut self) -> PResult { + let checkpoint = *self; let token = self.next_token(); match token.token { Token::Word(w) => Ok(w.to_ident()?), - unexpected => self.expected("identifier", unexpected.with_location(token.location)), + _ => self.expected_at(checkpoint, "identifier"), } } /// Parse a simple one-word identifier (possibly quoted, possibly a non-reserved keyword) - pub fn parse_identifier_non_reserved(&mut self) -> Result { + pub fn parse_identifier_non_reserved(&mut self) -> PResult { + let checkpoint = *self; let token = self.next_token(); - match token.token.clone() { + match token.token { Token::Word(w) => { match keywords::RESERVED_FOR_COLUMN_OR_TABLE_NAME.contains(&w.keyword) { - true => parser_err!(format!("syntax error at or near {token}")), + true => parser_err!("syntax error at or near {w}"), false => Ok(w.to_ident()?), } } - unexpected => self.expected("identifier", unexpected.with_location(token.location)), + _ => self.expected_at(checkpoint, "identifier"), } } /// Parse a parenthesized comma-separated list of unqualified, possibly quoted identifiers - pub fn parse_parenthesized_column_list( - &mut self, - optional: IsOptional, - ) -> Result, ParserError> { + pub fn parse_parenthesized_column_list(&mut self, optional: IsOptional) -> PResult> { if self.consume_token(&Token::LParen) { let cols = self.parse_comma_separated(Parser::parse_identifier_non_reserved)?; self.expect_token(&Token::RParen)?; @@ -4136,25 +3763,22 @@ impl Parser { } else if optional == Optional { Ok(vec![]) } else { - self.expected("a list of columns in parentheses", self.peek_token()) + self.expected("a list of columns in parentheses") } } - pub fn parse_returning( - &mut self, - optional: IsOptional, - ) -> Result, ParserError> { + pub fn parse_returning(&mut self, optional: IsOptional) -> PResult> { if self.parse_keyword(Keyword::RETURNING) { let cols = self.parse_comma_separated(Parser::parse_select_item)?; Ok(cols) } else if optional == Optional { Ok(vec![]) } else { - self.expected("a list of columns or * after returning", self.peek_token()) + self.expected("a list of columns or * after returning") } } - pub fn parse_row_expr(&mut self) -> Result { + pub fn parse_row_expr(&mut self) -> PResult { Ok(Expr::Row(self.parse_token_wrapped_exprs( &Token::LParen, &Token::RParen, @@ -4162,11 +3786,7 @@ impl Parser { } /// Parse a comma-separated list (maybe empty) from a wrapped expression - pub fn parse_token_wrapped_exprs( - &mut self, - left: &Token, - right: &Token, - ) -> Result, ParserError> { + pub fn parse_token_wrapped_exprs(&mut self, left: &Token, right: &Token) -> PResult> { if self.consume_token(left) { let exprs = if self.consume_token(right) { vec![] @@ -4177,11 +3797,11 @@ impl Parser { }; Ok(exprs) } else { - self.expected(left.to_string().as_str(), self.peek_token()) + self.expected(left.to_string().as_str()) } } - pub fn parse_optional_precision(&mut self) -> Result, ParserError> { + pub fn parse_optional_precision(&mut self) -> PResult> { if self.consume_token(&Token::LParen) { let n = self.parse_literal_uint()?; self.expect_token(&Token::RParen)?; @@ -4191,9 +3811,7 @@ impl Parser { } } - pub fn parse_optional_precision_scale( - &mut self, - ) -> Result<(Option, Option), ParserError> { + pub fn parse_optional_precision_scale(&mut self) -> PResult<(Option, Option)> { if self.consume_token(&Token::LParen) { let n = self.parse_literal_uint()?; let scale = if self.consume_token(&Token::Comma) { @@ -4208,7 +3826,7 @@ impl Parser { } } - pub fn parse_delete(&mut self) -> Result { + pub fn parse_delete(&mut self) -> PResult { self.expect_keyword(Keyword::FROM)?; let table_name = self.parse_object_name()?; let selection = if self.parse_keyword(Keyword::WHERE) { @@ -4237,7 +3855,7 @@ impl Parser { } } - pub fn parse_explain(&mut self) -> Result { + pub fn parse_explain(&mut self) -> PResult { let mut options = ExplainOptions::default(); let explain_key_words = [ @@ -4249,7 +3867,7 @@ impl Parser { Keyword::DISTSQL, ]; - let parse_explain_option = |parser: &mut Parser| -> Result<(), ParserError> { + let parse_explain_option = |parser: &mut Parser<'_>| -> PResult<()> { let keyword = parser.expect_one_of_keywords(&explain_key_words)?; match keyword { Keyword::VERBOSE => options.verbose = parser.parse_optional_boolean(true), @@ -4298,7 +3916,7 @@ impl Parser { /// preceded with some `WITH` CTE declarations and optionally followed /// by `ORDER BY`. Unlike some other parse_... methods, this one doesn't /// expect the initial keyword to be already consumed - pub fn parse_query(&mut self) -> Result { + pub fn parse_query(&mut self) -> PResult { let with = if self.parse_keyword(Keyword::WITH) { Some(With { recursive: self.parse_keyword(Keyword::RECURSIVE), @@ -4330,13 +3948,11 @@ impl Parser { let fetch = if self.parse_keyword(Keyword::FETCH) { if limit.is_some() { - return parser_err!("Cannot specify both LIMIT and FETCH".to_string()); + parser_err!("Cannot specify both LIMIT and FETCH"); } let fetch = self.parse_fetch()?; if fetch.with_ties && order_by.is_empty() { - return parser_err!( - "WITH TIES cannot be specified without ORDER BY clause".to_string() - ); + parser_err!("WITH TIES cannot be specified without ORDER BY clause"); } Some(fetch) } else { @@ -4354,39 +3970,38 @@ impl Parser { } /// Parse a CTE (`alias [( col1, col2, ... )] AS (subquery)`) - fn parse_cte(&mut self) -> Result { + fn parse_cte(&mut self) -> PResult { let name = self.parse_identifier_non_reserved()?; - - let mut cte = if self.parse_keyword(Keyword::AS) { - self.expect_token(&Token::LParen)?; - let query = self.parse_query()?; - self.expect_token(&Token::RParen)?; + let cte = if self.parse_keyword(Keyword::AS) { + let cte_inner = self.parse_cte_inner()?; let alias = TableAlias { name, columns: vec![], }; - Cte { - alias, - query, - from: None, - } + Cte { alias, cte_inner } } else { let columns = self.parse_parenthesized_column_list(Optional)?; self.expect_keyword(Keyword::AS)?; - self.expect_token(&Token::LParen)?; + let cte_inner = self.parse_cte_inner()?; + let alias = TableAlias { name, columns }; + Cte { alias, cte_inner } + }; + Ok(cte) + } + + fn parse_cte_inner(&mut self) -> PResult { + if let Ok(()) = self.expect_token(&Token::LParen) { let query = self.parse_query()?; self.expect_token(&Token::RParen)?; - let alias = TableAlias { name, columns }; - Cte { - alias, - query, - from: None, + Ok(CteInner::Query(query)) + } else { + let changelog = self.parse_identifier_non_reserved()?; + if changelog.to_string().to_lowercase() != "changelog" { + parser_err!("Expected 'changelog' but found '{}'", changelog); } - }; - if self.parse_keyword(Keyword::FROM) { - cte.from = Some(self.parse_identifier()?); + self.expect_keyword(Keyword::FROM)?; + Ok(CteInner::ChangeLog(self.parse_identifier()?)) } - Ok(cte) } /// Parse a "query body", which is an expression with roughly the @@ -4397,7 +4012,7 @@ impl Parser { /// subquery ::= query_body [ order_by_limit ] /// set_operation ::= query_body { 'UNION' | 'EXCEPT' | 'INTERSECT' } [ 'ALL' ] query_body /// ``` - fn parse_query_body(&mut self, precedence: u8) -> Result { + fn parse_query_body(&mut self, precedence: u8) -> PResult { // We parse the expression using a Pratt parser, as in `parse_expr()`. // Start by parsing a restricted SELECT or a `(subquery)`: let mut expr = if self.parse_keyword(Keyword::SELECT) { @@ -4410,10 +4025,7 @@ impl Parser { } else if self.parse_keyword(Keyword::VALUES) { SetExpr::Values(self.parse_values()?) } else { - return self.expected( - "SELECT, VALUES, or a subquery in the query body", - self.peek_token(), - ); + return self.expected("SELECT, VALUES, or a subquery in the query body"); }; loop { @@ -4453,7 +4065,7 @@ impl Parser { /// Parse a restricted `SELECT` statement (no CTEs / `UNION` / `ORDER BY`), /// assuming the initial `SELECT` was already consumed - pub fn parse_select(&mut self) -> Result { + pub fn parse_select(&mut self) -> PResult
) { let alias = self.gen_table_alias_with_prefix("with"); let (query, query_schema) = self.gen_local_query(); - let from = None; let cte = Cte { alias: alias.clone(), - query, - from, + cte_inner: risingwave_sqlparser::ast::CteInner::Query(query), }; let with_tables = vec![Table::new(alias.name.real_value(), query_schema)]; diff --git a/src/tests/sqlsmith/tests/frontend/mod.rs b/src/tests/sqlsmith/tests/frontend/mod.rs index 5c58f028b933d..6eedb4e41876b 100644 --- a/src/tests/sqlsmith/tests/frontend/mod.rs +++ b/src/tests/sqlsmith/tests/frontend/mod.rs @@ -186,26 +186,24 @@ fn run_batch_query( .bind(stmt) .map_err(|e| Failed::from(format!("Failed to bind:\nReason:\n{}", e.as_report())))?; let mut planner = Planner::new(context); - let mut logical_plan = planner.plan(bound).map_err(|e| { + let mut plan_root = planner.plan(bound).map_err(|e| { Failed::from(format!( "Failed to generate logical plan:\nReason:\n{}", e.as_report() )) })?; - let batch_plan = logical_plan.gen_batch_plan().map_err(|e| { + plan_root.gen_batch_plan().map_err(|e| { Failed::from(format!( "Failed to generate batch plan:\nReason:\n{}", e.as_report() )) })?; - logical_plan - .gen_batch_distributed_plan(batch_plan) - .map_err(|e| { - Failed::from(format!( - "Failed to generate batch distributed plan:\nReason:\n{}", - e.as_report() - )) - })?; + plan_root.gen_batch_distributed_plan().map_err(|e| { + Failed::from(format!( + "Failed to generate batch distributed plan:\nReason:\n{}", + e.as_report() + )) + })?; Ok(()) } diff --git a/src/utils/delta_btree_map/Cargo.toml b/src/utils/delta_btree_map/Cargo.toml index 48534ffad1867..274a028489395 100644 --- a/src/utils/delta_btree_map/Cargo.toml +++ b/src/utils/delta_btree_map/Cargo.toml @@ -15,7 +15,7 @@ ignored = ["workspace-hack"] normal = ["workspace-hack"] [dependencies] -educe = "0.5" +educe = "0.6" enum-as-inner = "0.6" [lints] diff --git a/src/utils/delta_btree_map/src/lib.rs b/src/utils/delta_btree_map/src/lib.rs index c300955af567b..1f7e1a77190a0 100644 --- a/src/utils/delta_btree_map/src/lib.rs +++ b/src/utils/delta_btree_map/src/lib.rs @@ -76,16 +76,18 @@ impl<'a, K: Ord, V> DeltaBTreeMap<'a, K, V> { pub fn find(&self, key: &K) -> Option> { let ss_cursor = self.snapshot.lower_bound(Bound::Included(key)); let dt_cursor = self.delta.lower_bound(Bound::Included(key)); - let curr_key_value = if dt_cursor.key() == Some(key) { - match dt_cursor.key_value().unwrap() { + let ss_cursor_kv = ss_cursor.peek_next(); + let dt_cursor_kv = dt_cursor.peek_next(); + let curr_key_value = if dt_cursor_kv.map(|(k, _)| k) == Some(key) { + match dt_cursor_kv.unwrap() { (key, Change::Insert(value)) => (key, value), (_key, Change::Delete) => { // the key is deleted return None; } } - } else if ss_cursor.key() == Some(key) { - ss_cursor.key_value().unwrap() + } else if ss_cursor_kv.map(|(k, _)| k) == Some(key) { + ss_cursor_kv.unwrap() } else { // the key doesn't exist return None; @@ -102,16 +104,8 @@ impl<'a, K: Ord, V> DeltaBTreeMap<'a, K, V> { // the implementation is very similar to `CursorWithDelta::peek_next` let mut ss_cursor = self.snapshot.lower_bound(bound); let mut dt_cursor = self.delta.lower_bound(bound); - let next_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_next(); - tmp - }; - let next_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_next(); - tmp - }; + let next_ss_entry = || ss_cursor.next(); + let next_dt_entry = || dt_cursor.next(); let curr_key_value = CursorWithDelta::peek_impl(PeekDirection::Next, next_ss_entry, next_dt_entry); CursorWithDelta { @@ -126,16 +120,8 @@ impl<'a, K: Ord, V> DeltaBTreeMap<'a, K, V> { // the implementation is very similar to `CursorWithDelta::peek_prev` let mut ss_cursor = self.snapshot.upper_bound(bound); let mut dt_cursor = self.delta.upper_bound(bound); - let prev_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_prev(); - tmp - }; - let prev_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_prev(); - tmp - }; + let prev_ss_entry = || ss_cursor.prev(); + let prev_dt_entry = || dt_cursor.prev(); let curr_key_value = CursorWithDelta::peek_impl(PeekDirection::Prev, prev_ss_entry, prev_dt_entry); CursorWithDelta { @@ -244,22 +230,14 @@ impl<'a, K: Ord, V> CursorWithDelta<'a, K, V> { let mut ss_cursor = self.snapshot.lower_bound(Bound::Included(key)); let mut dt_cursor = self.delta.lower_bound(Bound::Included(key)); // either one of `ss_cursor.key()` and `dt_cursor.key()` == `Some(key)`, or both are - if ss_cursor.key() == Some(key) { - ss_cursor.move_next(); + if ss_cursor.peek_next().map(|(k, _)| k) == Some(key) { + ss_cursor.next(); } - if dt_cursor.key() == Some(key) { - dt_cursor.move_next(); + if dt_cursor.peek_next().map(|(k, _)| k) == Some(key) { + dt_cursor.next(); } - let next_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_next(); - tmp - }; - let next_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_next(); - tmp - }; + let next_ss_entry = || ss_cursor.next(); + let next_dt_entry = || dt_cursor.next(); Self::peek_impl(PeekDirection::Next, next_ss_entry, next_dt_entry) } else { // we are at the ghost position, now let's go back to the beginning @@ -275,22 +253,14 @@ impl<'a, K: Ord, V> CursorWithDelta<'a, K, V> { let mut ss_cursor = self.snapshot.upper_bound(Bound::Included(key)); let mut dt_cursor = self.delta.upper_bound(Bound::Included(key)); // either one of `ss_cursor.key()` and `dt_cursor.key()` == `Some(key)`, or both are - if ss_cursor.key() == Some(key) { - ss_cursor.move_prev(); + if ss_cursor.peek_prev().map(|(k, _)| k) == Some(key) { + ss_cursor.prev(); } - if dt_cursor.key() == Some(key) { - dt_cursor.move_prev(); + if dt_cursor.peek_prev().map(|(k, _)| k) == Some(key) { + dt_cursor.prev(); } - let next_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_prev(); - tmp - }; - let next_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_prev(); - tmp - }; + let next_ss_entry = || ss_cursor.prev(); + let next_dt_entry = || dt_cursor.prev(); Self::peek_impl(PeekDirection::Prev, next_ss_entry, next_dt_entry) } else { // we are at the ghost position, now let's go back to the end @@ -317,8 +287,6 @@ impl<'a, K: Ord, V> CursorWithDelta<'a, K, V> { #[cfg(test)] mod tests { - use std::collections::BTreeMap; - use super::*; #[test] diff --git a/src/utils/futures_util/src/misc.rs b/src/utils/futures_util/src/misc.rs index f224b58214ddf..c6f5bcf07d23d 100644 --- a/src/utils/futures_util/src/misc.rs +++ b/src/utils/futures_util/src/misc.rs @@ -103,6 +103,12 @@ impl AttachedFuture { self.item.expect("should not be called after polled ready"), ) } + + pub fn item(&self) -> &T { + self.item + .as_ref() + .expect("should not be called after polled ready") + } } impl Future for AttachedFuture { diff --git a/src/utils/pgwire/src/lib.rs b/src/utils/pgwire/src/lib.rs index 96d439b8b94ff..2279156d89b37 100644 --- a/src/utils/pgwire/src/lib.rs +++ b/src/utils/pgwire/src/lib.rs @@ -19,6 +19,7 @@ #![feature(lazy_cell)] #![feature(buf_read_has_data_left)] #![feature(round_char_boundary)] +#![feature(never_type)] #![expect(clippy::doc_markdown, reason = "FIXME: later")] pub mod error; diff --git a/src/utils/pgwire/src/pg_field_descriptor.rs b/src/utils/pgwire/src/pg_field_descriptor.rs index 0b33c5743c107..82d75c78f7956 100644 --- a/src/utils/pgwire/src/pg_field_descriptor.rs +++ b/src/utils/pgwire/src/pg_field_descriptor.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PgFieldDescriptor { name: String, table_oid: i32, diff --git a/src/utils/pgwire/src/pg_message.rs b/src/utils/pgwire/src/pg_message.rs index 4898faf89b5d9..00c9a09b2eda6 100644 --- a/src/utils/pgwire/src/pg_message.rs +++ b/src/utils/pgwire/src/pg_message.rs @@ -697,7 +697,7 @@ macro_rules! from_usize { impl FromUsize for $t { #[inline] fn from_usize(x: usize) -> Result<$t> { - if x > <$t>::max_value() as usize { + if x > <$t>::MAX as usize { Err(Error::new(ErrorKind::InvalidInput, "value too large to transmit").into()) } else { Ok(x as $t) diff --git a/src/utils/pgwire/src/pg_protocol.rs b/src/utils/pgwire/src/pg_protocol.rs index 38eba6e92d5b6..d700e39757df1 100644 --- a/src/utils/pgwire/src/pg_protocol.rs +++ b/src/utils/pgwire/src/pg_protocol.rs @@ -29,7 +29,7 @@ use risingwave_common::types::DataType; use risingwave_common::util::panic::FutureCatchUnwindExt; use risingwave_common::util::query_log::*; use risingwave_common::{PG_VERSION, SERVER_ENCODING, STANDARD_CONFORMING_STRINGS}; -use risingwave_sqlparser::ast::Statement; +use risingwave_sqlparser::ast::{RedactSqlOptionKeywordsRef, Statement}; use risingwave_sqlparser::parser::Parser; use thiserror_ext::AsReport; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; @@ -101,6 +101,8 @@ where // Client Address peer_addr: AddressRef, + + redact_sql_option_keywords: Option, } /// Configures TLS encryption for connections. @@ -152,16 +154,31 @@ pub fn cstr_to_str(b: &Bytes) -> Result<&str, Utf8Error> { } /// Record `sql` in the current tracing span. -fn record_sql_in_span(sql: &str) { +fn record_sql_in_span(sql: &str, redact_sql_option_keywords: Option) { + let redacted_sql = if let Some(keywords) = redact_sql_option_keywords { + redact_sql(sql, keywords) + } else { + sql.to_owned() + }; tracing::Span::current().record( "sql", tracing::field::display(truncated_fmt::TruncatedFmt( - &sql, + &redacted_sql, *RW_QUERY_LOG_TRUNCATE_LEN, )), ); } +fn redact_sql(sql: &str, keywords: RedactSqlOptionKeywordsRef) -> String { + match Parser::parse_sql(sql) { + Ok(sqls) => sqls + .into_iter() + .map(|sql| sql.to_redacted_string(keywords.clone())) + .join(";"), + Err(_) => sql.to_owned(), + } +} + impl PgProtocol where S: AsyncWrite + AsyncRead + Unpin, @@ -172,6 +189,7 @@ where session_mgr: Arc, tls_config: Option, peer_addr: AddressRef, + redact_sql_option_keywords: Option, ) -> Self { Self { stream: Conn::Unencrypted(PgStream { @@ -193,6 +211,7 @@ where statement_portal_dependency: Default::default(), ignore_util_sync: false, peer_addr, + redact_sql_option_keywords, } } @@ -555,7 +574,7 @@ where async fn process_query_msg(&mut self, query_string: io::Result<&str>) -> PsqlResult<()> { let sql: Arc = Arc::from(query_string.map_err(|err| PsqlError::SimpleQueryError(Box::new(err)))?); - record_sql_in_span(&sql); + record_sql_in_span(&sql, self.redact_sql_option_keywords.clone()); let session = self.session.clone().unwrap(); session.check_idle_in_transaction_timeout()?; @@ -664,7 +683,7 @@ where fn process_parse_msg(&mut self, msg: FeParseMessage) -> PsqlResult<()> { let sql = cstr_to_str(&msg.sql_bytes).unwrap(); - record_sql_in_span(sql); + record_sql_in_span(sql, self.redact_sql_option_keywords.clone()); let session = self.session.clone().unwrap(); let statement_name = cstr_to_str(&msg.statement_name).unwrap().to_string(); @@ -767,7 +786,7 @@ where self.unnamed_portal.replace(portal); } else { assert!( - self.result_cache.get(&portal_name).is_none(), + !self.result_cache.contains_key(&portal_name), "Named portal never can be overridden." ); self.portal_store.insert(portal_name.clone(), portal); @@ -798,7 +817,7 @@ where } else { let portal = self.get_portal(&portal_name)?; let sql: Arc = Arc::from(format!("{}", portal)); - record_sql_in_span(&sql); + record_sql_in_span(&sql, self.redact_sql_option_keywords.clone()); session.check_idle_in_transaction_timeout()?; let _exec_context_guard = session.init_exec_context(sql.clone()); @@ -1205,3 +1224,25 @@ pub mod truncated_fmt { } } } + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use super::*; + + #[test] + fn test_redact_parsable_sql() { + let keywords = Arc::new(HashSet::from(["v2".into(), "v4".into(), "b".into()])); + let sql = r" + create source temp (k bigint, v varchar) with ( + connector = 'datagen', + v1 = 123, + v2 = 'with', + v3 = false, + v4 = '', + ) FORMAT plain ENCODE json (a='1',b='2') + "; + assert_eq!(redact_sql(sql, keywords), "CREATE SOURCE temp (k BIGINT, v CHARACTER VARYING) WITH (connector = 'datagen', v1 = 123, v2 = [REDACTED], v3 = false, v4 = [REDACTED]) FORMAT PLAIN ENCODE JSON (a = '1', b = [REDACTED])"); + } +} diff --git a/src/utils/pgwire/src/pg_response.rs b/src/utils/pgwire/src/pg_response.rs index 8e17423228af0..4f55c524942bc 100644 --- a/src/utils/pgwire/src/pg_response.rs +++ b/src/utils/pgwire/src/pg_response.rs @@ -26,6 +26,7 @@ use crate::types::Row; pub type RowSet = Vec; pub type RowSetResult = Result; + pub trait ValuesStream = Stream + Unpin + Send; #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -53,8 +54,10 @@ pub enum StatementType { CREATE_SCHEMA, CREATE_USER, CREATE_INDEX, + CREATE_AGGREGATE, CREATE_FUNCTION, CREATE_CONNECTION, + CREATE_SECRET, COMMENT, DECLARE_CURSOR, DESCRIBE, @@ -65,6 +68,7 @@ pub enum StatementType { DROP_VIEW, DROP_INDEX, DROP_FUNCTION, + DROP_AGGREGATE, DROP_SOURCE, DROP_SINK, DROP_SUBSCRIPTION, @@ -72,6 +76,7 @@ pub enum StatementType { DROP_DATABASE, DROP_USER, DROP_CONNECTION, + DROP_SECRET, ALTER_DATABASE, ALTER_SCHEMA, ALTER_INDEX, @@ -116,6 +121,7 @@ impl std::fmt::Display for StatementType { } pub trait Callback = Future> + Send; + pub type BoxedCallback = Pin>; pub struct PgResponse { @@ -291,6 +297,7 @@ impl StatementType { risingwave_sqlparser::ast::ObjectType::Connection => { Ok(StatementType::DROP_CONNECTION) } + risingwave_sqlparser::ast::ObjectType::Secret => Ok(StatementType::DROP_SECRET), risingwave_sqlparser::ast::ObjectType::Subscription => { Ok(StatementType::DROP_SUBSCRIPTION) } diff --git a/src/utils/pgwire/src/pg_server.rs b/src/utils/pgwire/src/pg_server.rs index 0538217de9acd..840f21dda1be2 100644 --- a/src/utils/pgwire/src/pg_server.rs +++ b/src/utils/pgwire/src/pg_server.rs @@ -14,8 +14,6 @@ use std::collections::HashMap; use std::future::Future; -use std::io; -use std::result::Result; use std::str::FromStr; use std::sync::Arc; use std::time::Instant; @@ -24,7 +22,9 @@ use bytes::Bytes; use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation}; use parking_lot::Mutex; use risingwave_common::types::DataType; -use risingwave_sqlparser::ast::Statement; +use risingwave_common::util::runtime::BackgroundShutdownRuntime; +use risingwave_common::util::tokio_util::sync::CancellationToken; +use risingwave_sqlparser::ast::{RedactSqlOptionKeywordsRef, Statement}; use serde::Deserialize; use thiserror_ext::AsReport; use tokio::io::{AsyncRead, AsyncWrite}; @@ -59,6 +59,11 @@ pub trait SessionManager: Send + Sync + 'static { fn cancel_creating_jobs_in_session(&self, session_id: SessionId); fn end_session(&self, session: &Self::Session); + + /// Run some cleanup tasks before the server shutdown. + fn shutdown(&self) -> impl Future + Send { + async {} + } } /// A psql connection. Each connection binds with a database. Switching database will need to @@ -248,32 +253,67 @@ impl UserAuthenticator { } /// Binds a Tcp or Unix listener at `addr`. Spawn a coroutine to serve every new connection. +/// +/// Returns when the `shutdown` token is triggered. pub async fn pg_serve( addr: &str, - session_mgr: Arc, + session_mgr: impl SessionManager, tls_config: Option, -) -> io::Result<()> { + redact_sql_option_keywords: Option, + shutdown: CancellationToken, +) -> Result<(), BoxedError> { let listener = Listener::bind(addr).await?; tracing::info!(addr, "server started"); - loop { - let conn_ret = listener.accept().await; - match conn_ret { - Ok((stream, peer_addr)) => { - tracing::info!(%peer_addr, "accept connection"); - tokio::spawn(handle_connection( - stream, - session_mgr.clone(), - tls_config.clone(), - Arc::new(peer_addr), - )); - } + let acceptor_runtime = BackgroundShutdownRuntime::from({ + let mut builder = tokio::runtime::Builder::new_multi_thread(); + builder.worker_threads(1); + builder + .thread_name("rw-acceptor") + .enable_all() + .build() + .unwrap() + }); - Err(e) => { - tracing::error!(error = %e.as_report(), "failed to accept connection",); + #[cfg(not(madsim))] + let worker_runtime = tokio::runtime::Handle::current(); + #[cfg(madsim)] + let worker_runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap(); + + let session_mgr = Arc::new(session_mgr); + let session_mgr_clone = session_mgr.clone(); + let f = async move { + loop { + let conn_ret = listener.accept().await; + match conn_ret { + Ok((stream, peer_addr)) => { + tracing::info!(%peer_addr, "accept connection"); + worker_runtime.spawn(handle_connection( + stream, + session_mgr_clone.clone(), + tls_config.clone(), + Arc::new(peer_addr), + redact_sql_option_keywords.clone(), + )); + } + + Err(e) => { + tracing::error!(error = %e.as_report(), "failed to accept connection",); + } } } - } + }; + acceptor_runtime.spawn(f); + + // Wait for the shutdown signal. + shutdown.cancelled().await; + + // Stop accepting new connections. + drop(acceptor_runtime); + // Shutdown session manager, typically close all existing sessions. + session_mgr.shutdown().await; + + Ok(()) } pub async fn handle_connection( @@ -281,11 +321,18 @@ pub async fn handle_connection( session_mgr: Arc, tls_config: Option, peer_addr: AddressRef, + redact_sql_option_keywords: Option, ) where S: AsyncWrite + AsyncRead + Unpin, SM: SessionManager, { - let mut pg_proto = PgProtocol::new(stream, session_mgr, tls_config, peer_addr); + let mut pg_proto = PgProtocol::new( + stream, + session_mgr, + tls_config, + peer_addr, + redact_sql_option_keywords, + ); loop { let msg = match pg_proto.read_message().await { Ok(msg) => msg, @@ -312,6 +359,7 @@ mod tests { use futures::stream::BoxStream; use futures::StreamExt; use risingwave_common::types::DataType; + use risingwave_common::util::tokio_util::sync::CancellationToken; use risingwave_sqlparser::ast::Statement; use tokio_postgres::NoTls; @@ -467,8 +515,17 @@ mod tests { let bind_addr = bind_addr.into(); let pg_config = pg_config.into(); - let session_mgr = Arc::new(MockSessionManager {}); - tokio::spawn(async move { pg_serve(&bind_addr, session_mgr, None).await }); + let session_mgr = MockSessionManager {}; + tokio::spawn(async move { + pg_serve( + &bind_addr, + session_mgr, + None, + None, + CancellationToken::new(), // dummy + ) + .await + }); // wait for server to start tokio::time::sleep(std::time::Duration::from_millis(100)).await; diff --git a/src/utils/runtime/Cargo.toml b/src/utils/runtime/Cargo.toml index a9ade03a9a46a..0559caee265d9 100644 --- a/src/utils/runtime/Cargo.toml +++ b/src/utils/runtime/Cargo.toml @@ -17,7 +17,7 @@ normal = ["workspace-hack"] [dependencies] await-tree = { workspace = true } console = "0.15" -console-subscriber = "0.2.0" +console-subscriber = "0.3.0" either = "1" futures = { version = "0.3", default-features = false, features = ["alloc"] } hostname = "0.4" @@ -56,6 +56,8 @@ opentelemetry_sdk = { workspace = true, features = [ "rt-tokio", ] } # only enable `rt-tokio` feature under non-madsim target workspace-hack = { path = "../../workspace-hack" } +minitrace = "0.6.7" +minitrace-opentelemetry = "0.6.7" [lints] workspace = true diff --git a/src/utils/runtime/src/lib.rs b/src/utils/runtime/src/lib.rs index b4501d991f05b..6418c18ea103a 100644 --- a/src/utils/runtime/src/lib.rs +++ b/src/utils/runtime/src/lib.rs @@ -19,8 +19,13 @@ #![feature(panic_update_hook)] #![feature(let_chains)] +#![feature(exitcode_exit_method)] + +use std::pin::pin; +use std::process::ExitCode; use futures::Future; +use risingwave_common::util::tokio_util::sync::CancellationToken; mod logger; pub use logger::*; @@ -33,7 +38,24 @@ use prof::*; /// Start RisingWave components with configs from environment variable. /// -/// Currently, the following env variables will be read: +/// # Shutdown on Ctrl-C +/// +/// The given closure `f` will take a [`CancellationToken`] as an argument. When a `SIGINT` signal +/// is received (typically by pressing `Ctrl-C`), [`CancellationToken::cancel`] will be called to +/// notify all subscribers to shutdown. You can use [`.cancelled()`](CancellationToken::cancelled) +/// to get notified on this. +/// +/// Users may also send a second `SIGINT` signal to force shutdown. In this case, this function +/// will exit the process with a non-zero exit code. +/// +/// When `f` returns, this function will assume that the component has finished its work and it's +/// safe to exit. Therefore, this function will exit the process with exit code 0 **without** +/// waiting for background tasks to finish. In other words, it's the responsibility of `f` to +/// ensure that all essential background tasks are finished before returning. +/// +/// # Environment variables +/// +/// Currently, the following environment variables will be read and used to configure the runtime. /// /// * `RW_WORKER_THREADS` (alias of `TOKIO_WORKER_THREADS`): number of tokio worker threads. If /// not set, it will be decided by tokio. Note that this can still be overridden by per-module @@ -42,9 +64,10 @@ use prof::*; /// debug mode, and disable in release mode. /// * `RW_PROFILE_PATH`: the path to generate flamegraph. If set, then profiling is automatically /// enabled. -pub fn main_okk(f: F) -> F::Output +pub fn main_okk(f: F) -> ! where - F: Future + Send + 'static, + F: FnOnce(CancellationToken) -> Fut, + Fut: Future + Send + 'static, { set_panic_hook(); @@ -73,10 +96,48 @@ where spawn_prof_thread(profile_path); } - tokio::runtime::Builder::new_multi_thread() + let future_with_shutdown = async move { + let shutdown = CancellationToken::new(); + let mut fut = pin!(f(shutdown.clone())); + + tokio::select! { + biased; + result = tokio::signal::ctrl_c() => { + result.expect("failed to receive ctrl-c signal"); + tracing::info!("received ctrl-c, shutting down... (press ctrl-c again to force shutdown)"); + + // Send shutdown signal. + shutdown.cancel(); + + // While waiting for the future to finish, listen for the second ctrl-c signal. + tokio::select! { + biased; + result = tokio::signal::ctrl_c() => { + result.expect("failed to receive ctrl-c signal"); + tracing::warn!("forced shutdown"); + + // Directly exit the process **here** instead of returning from the future, since + // we don't even want to run destructors but only exit as soon as possible. + ExitCode::FAILURE.exit_process(); + } + _ = &mut fut => {}, + } + } + _ = &mut fut => {}, + } + }; + + let runtime = tokio::runtime::Builder::new_multi_thread() .thread_name("rw-main") .enable_all() .build() - .unwrap() - .block_on(f) + .unwrap(); + + runtime.block_on(future_with_shutdown); + + // Shutdown the runtime and exit the process, without waiting for background tasks to finish. + // See the doc on this function for more details. + // TODO(shutdown): is it necessary to shutdown here as we're going to exit? + runtime.shutdown_background(); + ExitCode::SUCCESS.exit_process(); } diff --git a/src/utils/runtime/src/logger.rs b/src/utils/runtime/src/logger.rs index 7cc4194600b00..ee4bbd88d6f87 100644 --- a/src/utils/runtime/src/logger.rs +++ b/src/utils/runtime/src/logger.rs @@ -12,12 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::borrow::Cow; use std::env; use std::path::PathBuf; +use std::time::Duration; use either::Either; +use minitrace_opentelemetry::OpenTelemetryReporter; +use opentelemetry::trace::SpanKind; +use opentelemetry::InstrumentationLibrary; +use opentelemetry_sdk::Resource; use risingwave_common::metrics::MetricsLayer; use risingwave_common::util::deployment::Deployment; +use risingwave_common::util::env_var::env_var_is_true; use risingwave_common::util::query_log::*; use risingwave_common::util::tracing::layer::set_toggle_otel_layer_fn; use thiserror_ext::AsReport; @@ -170,6 +177,17 @@ fn disabled_filter() -> filter::Targets { /// /// `RW_QUERY_LOG_TRUNCATE_LEN` configures the max length of the SQLs logged in the query log, /// to avoid the log file growing too large. The default value is 1024 in production. +/// +/// ### `RW_ENABLE_PRETTY_LOG` +/// +/// If it is set to `true`, enable pretty log output, which contains line numbers and prints spans in multiple lines. +/// This can be helpful for development and debugging. +/// +/// Hint: Also turn off other uninteresting logs to make the most of the pretty log. +/// e.g., +/// ```bash +/// RUST_LOG="risingwave_storage::hummock::event_handler=off,batch_execute=off,risingwave_batch::task=off" RW_ENABLE_PRETTY_LOG=true risedev d +/// ``` pub fn init_risingwave_logger(settings: LoggerSettings) { let deployment = Deployment::current(); @@ -201,7 +219,7 @@ pub fn init_risingwave_logger(settings: LoggerSettings) { // Configure levels for external crates. filter = filter - .with_target("foyer", Level::WARN) + .with_target("foyer", Level::INFO) .with_target("aws", Level::INFO) .with_target("aws_config", Level::WARN) .with_target("aws_endpoint", Level::WARN) @@ -276,7 +294,13 @@ pub fn init_risingwave_logger(settings: LoggerSettings) { .json() .map_event_format(|e| e.with_current_span(false)) // avoid duplication as there's a span list field .boxed(), - Deployment::Other => fmt_layer.boxed(), + Deployment::Other => { + if env_var_is_true("RW_ENABLE_PRETTY_LOG") { + fmt_layer.pretty().boxed() + } else { + fmt_layer.boxed() + } + } }; layers.push( @@ -407,7 +431,7 @@ pub fn init_risingwave_logger(settings: LoggerSettings) { std::process::id() ); - let otel_tracer = { + let (otel_tracer, exporter) = { let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .thread_name("rw-otel") @@ -419,12 +443,12 @@ pub fn init_risingwave_logger(settings: LoggerSettings) { // Installing the exporter requires a tokio runtime. let _entered = runtime.enter(); - opentelemetry_otlp::new_pipeline() + let otel_tracer = opentelemetry_otlp::new_pipeline() .tracing() .with_exporter( opentelemetry_otlp::new_exporter() .tonic() - .with_endpoint(endpoint), + .with_endpoint(&endpoint), ) .with_trace_config(sdk::trace::config().with_resource(sdk::Resource::new([ KeyValue::new( @@ -433,12 +457,24 @@ pub fn init_risingwave_logger(settings: LoggerSettings) { // https://github.com/jaegertracing/jaeger-ui/issues/336 format!("{}-{}", settings.name, id), ), - KeyValue::new(resource::SERVICE_INSTANCE_ID, id), + KeyValue::new(resource::SERVICE_INSTANCE_ID, id.clone()), KeyValue::new(resource::SERVICE_VERSION, env!("CARGO_PKG_VERSION")), KeyValue::new(resource::PROCESS_PID, std::process::id().to_string()), ]))) .install_batch(sdk::runtime::Tokio) - .unwrap() + .unwrap(); + + let exporter = opentelemetry_otlp::new_exporter() + .tonic() + .with_endpoint(&endpoint) + .with_protocol(opentelemetry_otlp::Protocol::Grpc) + .with_timeout(Duration::from_secs( + opentelemetry_otlp::OTEL_EXPORTER_OTLP_TIMEOUT_DEFAULT, + )) + .build_span_exporter() + .unwrap(); + + (otel_tracer, exporter) }; // Disable by filtering out all events or spans by default. @@ -474,6 +510,27 @@ pub fn init_risingwave_logger(settings: LoggerSettings) { .with_filter(reload_filter); layers.push(layer.boxed()); + + // The reporter is used by minitrace in foyer for dynamically tail-based tracing. + // + // Code here only setup the OpenTelemetry reporter. To enable/disable the function, please use risectl. + // + // e.g. + // + // ```bash + // risectl hummock tiered-cache-tracing -h + // ``` + let reporter = OpenTelemetryReporter::new( + exporter, + SpanKind::Server, + Cow::Owned(Resource::new([KeyValue::new( + resource::SERVICE_NAME, + format!("minitrace-{id}"), + )])), + InstrumentationLibrary::builder("opentelemetry-instrumentation-foyer").build(), + ); + minitrace::set_reporter(reporter, minitrace::collector::Config::default()); + tracing::info!("opentelemetry exporter for minitrace is set at {endpoint}"); } // Metrics layer diff --git a/src/utils/workspace-config/Cargo.toml b/src/utils/workspace-config/Cargo.toml index e15dd774b7e3d..8972cb7c08475 100644 --- a/src/utils/workspace-config/Cargo.toml +++ b/src/utils/workspace-config/Cargo.toml @@ -11,7 +11,7 @@ repository = { workspace = true } [features] # some crates opt-in static linking, while some opt-in dynamic linking, # so they are two features :) -rw-static-link = ["static-libz-sys", "static-lzma-sys", "static-openssl-sys", "static-sasl2-sys"] +rw-static-link = ["static-libz-sys", "static-lzma-sys", "static-sasl2-sys"] rw-dynamic-link = ["dynamic-zstd-sys"] [dependencies] @@ -22,7 +22,6 @@ tracing = { version = "0.1", features = ["release_max_level_debug"] } # Static linking static-libz-sys = { package = "libz-sys", version = "1", optional = true, features = ["static"] } static-lzma-sys = { package = "lzma-sys", version = "0.1", optional = true, features = ["static"] } -static-openssl-sys = { package = "openssl-sys", version = "0.9.96", optional = true, features = ["vendored"] } static-sasl2-sys = { package = "sasl2-sys", version = "0.1", optional = true, features = ["gssapi-vendored"] } # Dynamic linking diff --git a/standalone/grafana.ini b/standalone/grafana.ini new file mode 100644 index 0000000000000..c05dee52e831a --- /dev/null +++ b/standalone/grafana.ini @@ -0,0 +1,10 @@ +[server] +http_addr = 0.0.0.0 +http_port = 3001 + +[users] +default_theme = light + +[auth.anonymous] +enabled = true +org_role = Admin \ No newline at end of file diff --git a/standalone/prometheus.yml b/standalone/prometheus.yml new file mode 100644 index 0000000000000..b354503515509 --- /dev/null +++ b/standalone/prometheus.yml @@ -0,0 +1,40 @@ +global: + scrape_interval: 15s + evaluation_interval: 60s + external_labels: + rw_cluster: 20240506-185437 + + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: ["127.0.0.1:9500"] + + - job_name: compute + static_configs: + - targets: ["127.0.0.1:1222"] + + - job_name: meta + static_configs: + - targets: ["127.0.0.1:1250"] + + - job_name: minio + metrics_path: /minio/v2/metrics/cluster + static_configs: + - targets: ["127.0.0.1:9301"] + + - job_name: compactor + static_configs: + - targets: ["127.0.0.1:1260"] + + - job_name: etcd + static_configs: + - targets: ["127.0.0.1:2379"] + + - job_name: frontend + static_configs: + - targets: ["127.0.0.1:2222"] + + - job_name: redpanda + static_configs: + - targets: []